Merge bk://openembedded@openembedded.bkbits.net/packages
authorPhil Blundell <philb@gnu.org>
Sat, 26 Jun 2004 10:46:28 +0000 (10:46 +0000)
committerPhil Blundell <philb@gnu.org>
Sat, 26 Jun 2004 10:46:28 +0000 (10:46 +0000)
into dixie.nexus.co.uk:/home/pb/oe/oe-packages

2004/06/26 11:46:12+01:00 nexus.co.uk!pb
enable MIPv6 for SIMpad kernels.
also enable more things as modules.

2004/06/26 11:44:33+01:00 nexus.co.uk!pb
add missing #include <linux/init.h>

BKrev: 40dd5404j5CiNtXiMLo-Tsu1pFAkkA

linux/files/mipv6-1.1-v2.4.25.patch
linux/opensimpad-2.4.25-vrs2-pxa1-jpm1/defconfig-simpad
linux/opensimpad_2.4.25-vrs2-pxa1-jpm1.oe

index e69de29..c5f32b6 100644 (file)
+diff -uprN linux-2.4.25.old/Documentation/Configure.help linux-2.4.25/Documentation/Configure.help
+--- linux-2.4.25.old/Documentation/Configure.help      2004-06-26 11:22:00.000000000 +0100
++++ linux-2.4.25/Documentation/Configure.help  2004-06-26 11:29:29.000000000 +0100
+@@ -6204,6 +6204,57 @@ CONFIG_IPV6
+   It is safe to say N here for now.
++IPv6: IPv6 over IPv6 Tunneling (EXPERIMENTAL)
++CONFIG_IPV6_TUNNEL
++  Experimental IP6-IP6 tunneling.  You must select this, if you want
++  to use CONFIG_IPV6_MOBILITY.  More information in MIPL Mobile IPv6
++  instructions.
++
++  If you don't want IP6-IP6 tunnels and Mobile IPv6, say N.
++
++IPv6: Mobility Support (EXPERIMENTAL)
++CONFIG_IPV6_MOBILITY
++  This is experimental support for the upcoming specification of
++  Mobile IPv6. Mobile IPv6 allows nodes to seamlessly move between
++  networks without changing their IP addresses, thus allowing them to
++  maintain upper layer connections (e.g. TCP).  Selecting this option
++  allows your computer to act as a Correspondent Node (CN).  A MIPv6
++  Mobile Node will be able to communicate with the CN and use route
++  optimization.
++
++  For more information and configuration details, see
++  http://www.mipl.mediapoli.com/.
++
++  If unsure, say N.
++
++MIPv6: Mobile Node Support
++CONFIG_IPV6_MOBILITY_MN
++  If you want your computer to be a MIPv6 Mobile Node (MN), select
++  this option.  You must configure MN using the userspace tools
++  available at http://www.mipl.mediapoli.com/download/mipv6-tools/.
++
++  If your computer is stationary, or you are unsure if you need this,
++  say N.  Note that you will need a properly configured MIPv6 Home
++  Agent to use any Mobile Nodes.
++
++MIPv6: Home Agent Support
++CONFIG_IPV6_MOBILITY_HA
++  If you want your router to serve as a MIPv6 Home Agent (HA), select
++  this option.  You must configure HA using the userspace tools
++  available at http://www.mipl.mediapoli.com/download/mipv6-tools/.
++
++  If your computer is not a router, or you are unsure if you need
++  this, say N.
++
++MIPv6: Debug messages
++CONFIG_IPV6_MOBILITY_DEBUG
++  MIPL Mobile IPv6 can produce a lot of debugging messages. There are
++  eight debug levels (0 through 7) and the level is controlled via
++  /proc/sys/net/ipv6/mobility/debuglevel. Since MIPL is still
++  experimental, you might want to say Y here.
++
++  Be sure to say Y and record debug messages when submitting a bug
++  report.
+ The SCTP Protocol (EXPERIMENTAL)
+ CONFIG_IP_SCTP
+   Stream Control Transmission Protocol
+diff -uprN linux-2.4.25.old/Documentation/DocBook/Makefile linux-2.4.25/Documentation/DocBook/Makefile
+--- linux-2.4.25.old/Documentation/DocBook/Makefile    2002-11-28 23:53:08.000000000 +0000
++++ linux-2.4.25/Documentation/DocBook/Makefile        2004-06-26 11:29:29.000000000 +0100
+@@ -2,7 +2,7 @@ BOOKS  := wanbook.sgml z8530book.sgml mca
+          kernel-api.sgml parportbook.sgml kernel-hacking.sgml \
+          kernel-locking.sgml via-audio.sgml mousedrivers.sgml sis900.sgml \
+          deviceiobook.sgml procfs-guide.sgml tulip-user.sgml \
+-         journal-api.sgml
++         journal-api.sgml mip6-func.sgml
+ PS    :=      $(patsubst %.sgml, %.ps, $(BOOKS))
+ PDF   :=      $(patsubst %.sgml, %.pdf, $(BOOKS))
+@@ -86,6 +86,9 @@ videobook.sgml: videobook.tmpl $(TOPDIR)
+ procfs-guide.sgml:  procfs-guide.tmpl procfs_example.sgml
+       $(TOPDIR)/scripts/docgen < procfs-guide.tmpl >$@
++mip6-func.sgml: mip6-func.tmpl
++      $(TOPDIR)/scripts/docgen <$< >$@
++
+ APISOURCES := $(TOPDIR)/drivers/media/video/videodev.c \
+               $(TOPDIR)/arch/i386/kernel/irq.c \
+               $(TOPDIR)/arch/i386/kernel/mca.c \
+diff -uprN linux-2.4.25.old/Documentation/DocBook/mip6-func.tmpl linux-2.4.25/Documentation/DocBook/mip6-func.tmpl
+--- linux-2.4.25.old/Documentation/DocBook/mip6-func.tmpl      1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/Documentation/DocBook/mip6-func.tmpl  2004-06-26 11:29:29.000000000 +0100
+@@ -0,0 +1,756 @@
++<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook V3.1//EN"[]>
++<book id="LinuxMobileIPv6">
++ <bookinfo>
++  <title>MIPL Mobile IPv6 Function Reference Guide</title>
++
++  <authorgroup>
++    <author>
++      <othername>MIPL Mobile IPv6 for Linux Team</othername>
++      <affiliation>
++       <orgname>Helsinki University of Technology</orgname>
++       <orgdiv>Telecommunications Software and Multimedia Lab</orgdiv>
++       <address>
++        <pob>PO BOX 9201</pob>
++      <postcode>FIN-02015 HUT</postcode>
++      <country>Finland</country>
++        <email>mipl@list.mipl.mediapoli.com</email>
++       </address>
++      </affiliation>
++     </author>
++  </authorgroup>
++
++  <copyright>
++   <year>2000-2001</year>
++   <holder>Helsinki University of Technology</holder>
++  </copyright>
++ 
++  <legalnotice>
++   <para>
++     Copyright (c) 2001, 2002 MIPL Mobile IPv6 for Linux Team.
++   </para>
++   <para>
++     Permission is granted to copy, distribute and/or modify this
++     document under the terms of the GNU Free Documentation License,
++     Version 1.1 published by the Free Software Foundation; with the
++     Invariant Sections being "Introduction", with the Front-Cover
++     Texts being "MIPL Mobile IPv6 Function Reference Guide", "MIPL
++     Mobile IPv6 for Linux Team" and "Helsinki University of
++     Technology".  A copy of the license is included in <xref
++     linkend="gfdl">.
++   </para>
++      
++  </legalnotice>
++ </bookinfo>
++
++<toc></toc>
++
++  <preface id="intro">
++     <title>Introduction</title>
++
++     <para>
++       MIPL Mobile IPv6 for Linux is an implementation of Mobility
++       Support in IPv6 IETF mobile-ip working groups Internet-Draft
++       (draft-ietf-mobileip-ipv6).  This implementation has been
++       developed in the Telecommunications Software and Multimedia
++       Laboratory at Helsinki University of Technology.
++     </para>
++
++     <para>
++       MIPL is fully open source, licensed under the GNU General
++       Public License.  Latest source for MIPL can be downloaded from
++       the MIPL website at:
++     </para>
++     <programlisting>
++       http://www.mipl.mediapoli.com/.
++     </programlisting>
++     <para>
++       Developers and users interested in MIPL can subscribe to the
++       MIPL mailing list by sending e-mail to
++       <email>majordomo@list.mipl.mediapoli.com</email> with
++     </para>
++     <programlisting>
++       subscribe mipl
++     </programlisting>
++     <para>
++       in the body of the message.
++     </para>
++
++     <para>
++       This document is a reference guide to MIPL functions.  Intended
++       audience is developers wishing to contribute to the project.
++       Hopefully this document will make it easier and quicker to
++       understand and adopt the inner workings of MIPL Mobile IPv6.
++     </para>
++
++     <para>
++       MIPL Mobile IPv6 for Linux Team members (past and present):
++
++       <itemizedlist>
++        <listitem>
++      <address>
++      Sami Kivisaari <email>Sami.Kivisaari@hut.fi</email>
++      </address>
++        </listitem>
++        <listitem>
++      <address>
++      Niklas Kampe <email>Niklas.Kampe@hut.fi</email>
++      </address>
++        </listitem>
++        <listitem>
++      <address>
++      Juha Mynttinen <email>Juha.Mynttinen@hut.fi</email>
++      </address>
++        </listitem>
++        <listitem>
++      <address>
++      Toni Nykanen <email>Toni.Nykanen@iki.fi</email>
++      </address>
++        </listitem>
++        <listitem>
++      <address>
++      Henrik Petander <email>Henrik.Petander@hut.fi</email>
++      </address>
++        </listitem>
++        <listitem>
++      <address>
++      Antti Tuominen <email>ajtuomin@tml.hut.fi</email>
++      </address>
++        </listitem>
++       </itemizedlist>
++
++       <itemizedlist>
++        <listitem>
++      <address>
++      Marko Myllynen
++      </address>
++        </listitem>
++        <listitem>
++      <address>
++      Ville Nuorvala <email>vnuorval@tcs.hut.fi</email>
++      </address>
++        </listitem>
++        <listitem>
++      <address>
++      Jaakko Laine <email>Jaakko.Laine@hut.fi</email>
++      </address>
++        </listitem>
++       </itemizedlist>
++     </para>
++
++  </preface>
++
++  <chapter id="common">
++     <title>Common functions for all entities</title>
++
++     <sect1><title>Low-level functions</title>
++     <para>
++       These functions implement memory allocation used by others.
++       Hashlist functions implement a linked list with hash lookup,
++       which is used with Binding Update List, Binding Cache, Home
++       Agents List etc.
++     </para>
++!Inet/ipv6/mobile_ip6/mempool.h
++!Inet/ipv6/mobile_ip6/hashlist.h
++     </sect1>
++
++     <sect1><title>Debug functions</title>
++     <para>
++       Debug and utility functions.  These functions are available if
++       <constant>CONFIG_IPV6_MOBILITY_DEBUG</constant> is set.
++       Otherwise macros expand to no operation.
++     </para>
++!Inet/ipv6/mobile_ip6/debug.h
++!Inet/ipv6/mobile_ip6/mipv6.c
++     </sect1>
++
++     <sect1><title>Extension Header functions</title>
++     <para>
++       These functions create and handle extension headers that are
++       specific to MIPv6.
++     </para>
++!Inet/ipv6/mobile_ip6/exthdrs.c
++     </sect1>
++
++     <sect1><title>Mobility Header functions</title>
++     <para>
++       MIPv6 specifies a new protocol called Mobility Header.
++       Mobility Header has several message types.  Messages may also
++       carry Mobility Options.  These functions are used to create and
++       handle Mobility Headers and Mobility Options.
++     </para>
++!Inet/ipv6/mobile_ip6/sendopts.c
++!Inet/ipv6/mobile_ip6/mh_recv.c
++!Inet/ipv6/mobile_ip6/auth_subopt.c
++     </sect1>
++
++     <sect1><title>Binding Cache</title>
++     <para>
++       All Mobile IPv6 entities have a binding cache.  These functions
++       provide easy manipulation of the binding cache.
++     </para>
++!Inet/ipv6/mobile_ip6/bcache.c
++     </sect1>
++
++     <sect1><title>Security</title>
++
++     <para>
++       These functions are common authentication functions and
++       implement Draft 13 style IPSec AH support for Binding Updates.
++     </para>
++!Inet/ipv6/mobile_ip6/ah_algo.c
++!Inet/ipv6/mobile_ip6/sadb.c
++!Inet/ipv6/mobile_ip6/ah.c
++     </sect1>
++
++     <sect1><title>Utility functions</title>
++
++     <para>
++       These functions are general utility functions commonly used by
++       all entities.
++     </para>
++!Inet/ipv6/mobile_ip6/util.c
++     </sect1>
++
++  </chapter>
++
++  <chapter id="mn">
++     <title>Mobile Node functions</title>
++     <sect1><title>General functions</title>
++     <para>
++     </para>
++!Inet/ipv6/mobile_ip6/mn.c
++     </sect1>
++
++     <sect1><title>Binding Update List</title>
++     <para>
++       Mobile Node keeps track of sent binding updates in Binding
++       Update List.
++     </para>
++!Inet/ipv6/mobile_ip6/bul.c
++     </sect1>
++
++     <sect1><title>Movement detection</title>
++
++     <para>
++       These functions are used by the mobile node for movement
++       detection.
++     </para>
++!Inet/ipv6/mobile_ip6/mdetect.c
++     </sect1>
++  </chapter>
++
++  <chapter id="ha">
++     <title>Home Agent functions</title>
++     <sect1><title>General functions</title>
++     <para>
++     </para>
++!Inet/ipv6/mobile_ip6/ha.c
++     </sect1>
++
++     <sect1><title>Duplicate Address Detection functions</title>
++     <para>
++       Home Agent does Duplicate Address Detection for Mobile Nodes'
++       addresses.  These functions implement MIPv6 specific DAD
++       functionality.
++     </para>
++!Inet/ipv6/mobile_ip6/dad.c
++     </sect1>
++
++  </chapter>
++  <appendix id="gfdl">
++     <title>GNU Free Documentation License</title>
++
++     <para>
++       Version 1.1, March 2000
++     </para>
++
++     <programlisting>
++       Copyright (C) 2000  Free Software Foundation, Inc.
++       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++       Everyone is permitted to copy and distribute verbatim copies
++       of this license document, but changing it is not allowed.
++     </programlisting>
++
++     <sect1><title>0. PREAMBLE</title>
++
++     <para>
++       The purpose of this License is to make a manual, textbook, or
++       other written document "free" in the sense of freedom: to
++       assure everyone the effective freedom to copy and redistribute
++       it, with or without modifying it, either commercially or
++       noncommercially. Secondarily, this License preserves for the
++       author and publisher a way to get credit for their work, while
++       not being considered responsible for modifications made by
++       others.
++     </para>
++
++     <para>
++       This License is a kind of "copyleft", which means that
++       derivative works of the document must themselves be free in the
++       same sense. It complements the GNU General Public License,
++       which is a copyleft license designed for free software.
++     </para>
++
++     <para>
++       We have designed this License in order to use it for manuals
++       for free software, because free software needs free
++       documentation: a free program should come with manuals
++       providing the same freedoms that the software does. But this
++       License is not limited to software manuals; it can be used for
++       any textual work, regardless of subject matter or whether it is
++       published as a printed book. We recommend this License
++       principally for works whose purpose is instruction or
++       reference.
++     </para>
++
++     </sect1>
++     <sect1><title>1. APPLICABILITY AND DEFINITIONS</title>
++
++     <para>
++       This License applies to any manual or other work that contains
++       a notice placed by the copyright holder saying it can be
++       distributed under the terms of this License. The "Document",
++       below, refers to any such manual or work. Any member of the
++       public is a licensee, and is addressed as "you".
++     </para>
++
++     <para>
++       A "Modified Version" of the Document means any work containing
++       the Document or a portion of it, either copied verbatim, or
++       with modifications and/or translated into another language.
++     </para>
++
++     <para>
++       A "Secondary Section" is a named appendix or a front-matter
++       section of the Document that deals exclusively with the
++       relationship of the publishers or authors of the Document to
++       the Document's overall subject (or to related matters) and
++       contains nothing that could fall directly within that overall
++       subject. (For example, if the Document is in part a textbook of
++       mathematics, a Secondary Section may not explain any
++       mathematics.) The relationship could be a matter of historical
++       connection with the subject or with related matters, or of
++       legal, commercial, philosophical, ethical or political position
++       regarding them.
++     </para>
++
++     <para>
++       The "Invariant Sections" are certain Secondary Sections whose
++       titles are designated, as being those of Invariant Sections, in
++       the notice that says that the Document is released under this
++       License.
++     </para>
++
++     <para>
++       The "Cover Texts" are certain short passages of text that are
++       listed, as Front-Cover Texts or Back-Cover Texts, in the notice
++       that says that the Document is released under this License.
++     </para>
++
++     <para>
++       A "Transparent" copy of the Document means a machine-readable
++       copy, represented in a format whose specification is available
++       to the general public, whose contents can be viewed and edited
++       directly and straightforwardly with generic text editors or
++       (for images composed of pixels) generic paint programs or (for
++       drawings) some widely available drawing editor, and that is
++       suitable for input to text formatters or for automatic
++       translation to a variety of formats suitable for input to text
++       formatters. A copy made in an otherwise Transparent file format
++       whose markup has been designed to thwart or discourage
++       subsequent modification by readers is not Transparent. A copy
++       that is not "Transparent" is called "Opaque".
++     </para>
++
++     <para>
++       Examples of suitable formats for Transparent copies include
++       plain ASCII without markup, Texinfo input format, LaTeX input
++       format, SGML or XML using a publicly available DTD, and
++       standard-conforming simple HTML designed for human
++       modification. Opaque formats include PostScript, PDF,
++       proprietary formats that can be read and edited only by
++       proprietary word processors, SGML or XML for which the DTD
++       and/or processing tools are not generally available, and the
++       machine-generated HTML produced by some word processors for
++       output purposes only.
++     </para>
++
++     <para>
++       The "Title Page" means, for a printed book, the title page
++       itself, plus such following pages as are needed to hold,
++       legibly, the material this License requires to appear in the
++       title page. For works in formats which do not have any title
++       page as such, "Title Page" means the text near the most
++       prominent appearance of the work's title, preceding the
++       beginning of the body of the text.
++     </para>
++
++     </sect1>
++     <sect1><title>2. VERBATIM COPYING</title>
++
++     <para>
++       You may copy and distribute the Document in any medium, either
++       commercially or noncommercially, provided that this License,
++       the copyright notices, and the license notice saying this
++       License applies to the Document are reproduced in all copies,
++       and that you add no other conditions whatsoever to those of
++       this License. You may not use technical measures to obstruct or
++       control the reading or further copying of the copies you make
++       or distribute. However, you may accept compensation in exchange
++       for copies. If you distribute a large enough number of copies
++       you must also follow the conditions in section 3.
++     </para>
++
++     <para>
++       You may also lend copies, under the same conditions stated
++       above, and you may publicly display copies.
++     </para>
++
++     </sect1>
++     <sect1><title>3. COPYING IN QUANTITY</title>
++
++     <para>
++       If you publish printed copies of the Document numbering more
++       than 100, and the Document's license notice requires Cover
++       Texts, you must enclose the copies in covers that carry,
++       clearly and legibly, all these Cover Texts: Front-Cover Texts
++       on the front cover, and Back-Cover Texts on the back
++       cover. Both covers must also clearly and legibly identify you
++       as the publisher of these copies. The front cover must present
++       the full title with all words of the title equally prominent
++       and visible. You may add other material on the covers in
++       addition. Copying with changes limited to the covers, as long
++       as they preserve the title of the Document and satisfy these
++       conditions, can be treated as verbatim copying in other
++       respects.
++     </para>
++
++     <para>
++       If the required texts for either cover are too voluminous to
++       fit legibly, you should put the first ones listed (as many as
++       fit reasonably) on the actual cover, and continue the rest onto
++       adjacent pages.
++     </para>
++
++     <para>
++       If you publish or distribute Opaque copies of the Document
++       numbering more than 100, you must either include a
++       machine-readable Transparent copy along with each Opaque copy,
++       or state in or with each Opaque copy a publicly-accessible
++       computer-network location containing a complete Transparent
++       copy of the Document, free of added material, which the general
++       network-using public has access to download anonymously at no
++       charge using public-standard network protocols. If you use the
++       latter option, you must take reasonably prudent steps, when you
++       begin distribution of Opaque copies in quantity, to ensure that
++       this Transparent copy will remain thus accessible at the stated
++       location until at least one year after the last time you
++       distribute an Opaque copy (directly or through your agents or
++       retailers) of that edition to the public.
++     </para>
++
++     <para>
++       It is requested, but not required, that you contact the authors
++       of the Document well before redistributing any large number of
++       copies, to give them a chance to provide you with an updated
++       version of the Document.
++     </para>
++
++     </sect1>
++     <sect1><title>4. MODIFICATIONS</title>
++
++     <para>
++       You may copy and distribute a Modified Version of the Document
++       under the conditions of sections 2 and 3 above, provided that
++       you release the Modified Version under precisely this License,
++       with the Modified Version filling the role of the Document,
++       thus licensing distribution and modification of the Modified
++       Version to whoever possesses a copy of it. In addition, you
++       must do these things in the Modified Version:
++     </para>
++
++     <para>
++      <itemizedlist spacing=compact>
++       <listitem>
++        <para>
++        A. Use in the Title Page (and on the covers, if any) a title
++        distinct from that of the Document, and from those of previous
++        versions (which should, if there were any, be listed in the
++        History section of the Document). You may use the same title
++        as a previous version if the original publisher of that
++        version gives permission.
++      </para>
++       </listitem>
++       <listitem>
++        <para>
++      B. List on the Title Page, as authors, one or more persons
++      or entities responsible for authorship of the modifications in
++      the Modified Version, together with at least five of the
++      principal authors of the Document (all of its principal
++      authors, if it has less than five).
++      </para>
++       </listitem>
++       <listitem>
++        <para>
++      C. State on the Title page the name of the publisher of the
++      Modified Version, as the publisher.
++      </para>
++       </listitem>
++       <listitem>
++        <para>
++      D. Preserve all the copyright notices of the Document.
++      </para>
++       </listitem>
++       <listitem>
++        <para>
++      E. Add an appropriate copyright notice for your
++      modifications adjacent to the other copyright notices.
++      </para>
++       </listitem>
++       <listitem>
++        <para>
++      F. Include, immediately after the copyright notices, a
++      license notice giving the public permission to use the
++      Modified Version under the terms of this License, in the form
++      shown in the Addendum below.
++      </para>
++       </listitem>
++       <listitem>
++        <para>
++      G. Preserve in that license notice the full lists of
++      Invariant Sections and required Cover Texts given in the
++      Document's license notice.
++      </para>
++       </listitem>
++       <listitem>
++        <para>
++      H. Include an unaltered copy of this License.
++      </para>
++       </listitem>
++       <listitem>
++        <para>
++      I. Preserve the section entitled "History", and its title,
++      and add to it an item stating at least the title, year, new
++      authors, and publisher of the Modified Version as given on the
++      Title Page. If there is no section entitled "History" in the
++      Document, create one stating the title, year, authors, and
++      publisher of the Document as given on its Title Page, then add
++      an item describing the Modified Version as stated in the
++      previous sentence.
++      </para>
++       </listitem>
++       <listitem>
++        <para>
++        J. Preserve the network location, if any, given in the
++        Document for public access to a Transparent copy of the
++        Document, and likewise the network locations given in the
++        Document for previous versions it was based on. These may be
++        placed in the "History" section. You may omit a network
++        location for a work that was published at least four years
++        before the Document itself, or if the original publisher of
++        the version it refers to gives permission.
++      </para>
++       </listitem>
++       <listitem>
++        <para>
++        K. In any section entitled "Acknowledgements" or
++        "Dedications", preserve the section's title, and preserve in
++        the section all the substance and tone of each of the
++        contributor acknowledgements and/or dedications given therein.
++      </para>
++       </listitem>
++       <listitem>
++        <para>
++      L. Preserve all the Invariant Sections of the Document,
++      unaltered in their text and in their titles. Section numbers
++      or the equivalent are not considered part of the section
++      titles.
++      </para>
++       </listitem>
++       <listitem>
++        <para>
++      M. Delete any section entitled "Endorsements". Such a
++      section may not be included in the Modified Version.
++      </para>
++       </listitem>
++       <listitem>
++        <para>
++      N. Do not retitle any existing section as "Endorsements" or
++      to conflict in title with any Invariant Section.
++      </para>
++       </listitem>
++      </itemizedlist>
++     </para>
++
++     <para>
++       If the Modified Version includes new front-matter sections or
++       appendices that qualify as Secondary Sections and contain no
++       material copied from the Document, you may at your option
++       designate some or all of these sections as invariant. To do
++       this, add their titles to the list of Invariant Sections in the
++       Modified Version's license notice. These titles must be
++       distinct from any other section titles.
++     </para>
++
++     <para>
++       You may add a section entitled "Endorsements", provided it
++       contains nothing but endorsements of your Modified Version by
++       various parties--for example, statements of peer review or that
++       the text has been approved by an organization as the
++       authoritative definition of a standard.
++     </para>
++
++     <para>
++       You may add a passage of up to five words as a Front-Cover
++       Text, and a passage of up to 25 words as a Back-Cover Text, to
++       the end of the list of Cover Texts in the Modified
++       Version. Only one passage of Front-Cover Text and one of
++       Back-Cover Text may be added by (or through arrangements made
++       by) any one entity. If the Document already includes a cover
++       text for the same cover, previously added by you or by
++       arrangement made by the same entity you are acting on behalf
++       of, you may not add another; but you may replace the old one,
++       on explicit permission from the previous publisher that added
++       the old one.
++     </para>
++
++     <para>
++       The author(s) and publisher(s) of the Document do not by this
++       License give permission to use their names for publicity for or
++       to assert or imply endorsement of any Modified Version.
++     </para>
++
++     </sect1>
++     <sect1><title>5. COMBINING DOCUMENTS</title>
++
++     <para>
++       You may combine the Document with other documents released
++       under this License, under the terms defined in section 4 above
++       for modified versions, provided that you include in the
++       combination all of the Invariant Sections of all of the
++       original documents, unmodified, and list them all as Invariant
++       Sections of your combined work in its license notice.
++     </para>
++
++     <para>
++       The combined work need only contain one copy of this License,
++       and multiple identical Invariant Sections may be replaced with
++       a single copy. If there are multiple Invariant Sections with
++       the same name but different contents, make the title of each
++       such section unique by adding at the end of it, in parentheses,
++       the name of the original author or publisher of that section if
++       known, or else a unique number. Make the same adjustment to the
++       section titles in the list of Invariant Sections in the license
++       notice of the combined work.
++     </para>
++
++     <para>
++       In the combination, you must combine any sections entitled
++       "History" in the various original documents, forming one
++       section entitled "History"; likewise combine any sections
++       entitled "Acknowledgements", and any sections entitled
++       "Dedications". You must delete all sections entitled
++       "Endorsements."
++     </para>
++
++     </sect1>
++     <sect1><title>6. COLLECTIONS OF DOCUMENTS</title>
++
++     <para>
++       You may make a collection consisting of the Document and other
++       documents released under this License, and replace the
++       individual copies of this License in the various documents with
++       a single copy that is included in the collection, provided that
++       you follow the rules of this License for verbatim copying of
++       each of the documents in all other respects.
++     </para>
++
++     <para>
++       You may extract a single document from such a collection, and
++       distribute it individually under this License, provided you
++       insert a copy of this License into the extracted document, and
++       follow this License in all other respects regarding verbatim
++       copying of that document.
++     </para>
++
++     </sect1>
++     <sect1><title>7. AGGREGATION WITH INDEPENDENT WORKS</title>
++
++     <para>
++       A compilation of the Document or its derivatives with other
++       separate and independent documents or works, in or on a volume
++       of a storage or distribution medium, does not as a whole count
++       as a Modified Version of the Document, provided no compilation
++       copyright is claimed for the compilation. Such a compilation is
++       called an "aggregate", and this License does not apply to the
++       other self-contained works thus compiled with the Document, on
++       account of their being thus compiled, if they are not
++       themselves derivative works of the Document.
++     </para>
++
++     <para>
++       If the Cover Text requirement of section 3 is applicable to
++       these copies of the Document, then if the Document is less than
++       one quarter of the entire aggregate, the Document's Cover Texts
++       may be placed on covers that surround only the Document within
++       the aggregate. Otherwise they must appear on covers around the
++       whole aggregate.
++     </para>
++
++     </sect1>
++     <sect1><title>8. TRANSLATION</title>
++
++     <para>
++       Translation is considered a kind of modification, so you may
++       distribute translations of the Document under the terms of
++       section 4. Replacing Invariant Sections with translations
++       requires special permission from their copyright holders, but
++       you may include translations of some or all Invariant Sections
++       in addition to the original versions of these Invariant
++       Sections. You may include a translation of this License
++       provided that you also include the original English version of
++       this License. In case of a disagreement between the translation
++       and the original English version of this License, the original
++       English version will prevail.
++     </para>
++
++     </sect1>
++     <sect1><title>9. TERMINATION</title>
++
++     <para>
++       You may not copy, modify, sublicense, or distribute the
++       Document except as expressly provided for under this
++       License. Any other attempt to copy, modify, sublicense or
++       distribute the Document is void, and will automatically
++       terminate your rights under this License. However, parties who
++       have received copies, or rights, from you under this License
++       will not have their licenses terminated so long as such parties
++       remain in full compliance.
++     </para>
++
++     </sect1>
++     <sect1><title>10. FUTURE REVISIONS OF THIS LICENSE</title>
++
++     <para>
++       The Free Software Foundation may publish new, revised versions
++       of the GNU Free Documentation License from time to time. Such
++       new versions will be similar in spirit to the present version,
++       but may differ in detail to address new problems or
++       concerns. See http://www.gnu.org/copyleft/.
++     </para>
++
++     <para>
++       Each version of the License is given a distinguishing version
++       number. If the Document specifies that a particular numbered
++       version of this License "or any later version" applies to it,
++       you have the option of following the terms and conditions
++       either of that specified version or of any later version that
++       has been published (not as a draft) by the Free Software
++       Foundation. If the Document does not specify a version number
++       of this License, you may choose any version ever published (not
++       as a draft) by the Free Software Foundation.
++     </para>
++
++     </sect1>
++  </appendix>
++</book>
+diff -uprN linux-2.4.25.old/include/linux/icmpv6.h linux-2.4.25/include/linux/icmpv6.h
+--- linux-2.4.25.old/include/linux/icmpv6.h    2003-08-25 12:44:44.000000000 +0100
++++ linux-2.4.25/include/linux/icmpv6.h        2004-06-26 11:29:29.000000000 +0100
+@@ -40,14 +40,16 @@ struct icmp6hdr {
+                 struct icmpv6_nd_ra {
+                       __u8            hop_limit;
+ #if defined(__LITTLE_ENDIAN_BITFIELD)
+-                      __u8            reserved:6,
++                      __u8            reserved:5,
++                                      home_agent:1,
+                                       other:1,
+                                       managed:1;
+ #elif defined(__BIG_ENDIAN_BITFIELD)
+                       __u8            managed:1,
+                                       other:1,
+-                                      reserved:6;
++                                      home_agent:1,
++                                      reserved:5;
+ #else
+ #error        "Please fix <asm/byteorder.h>"
+ #endif
+@@ -70,6 +72,7 @@ struct icmp6hdr {
+ #define icmp6_addrconf_managed        icmp6_dataun.u_nd_ra.managed
+ #define icmp6_addrconf_other  icmp6_dataun.u_nd_ra.other
+ #define icmp6_rt_lifetime     icmp6_dataun.u_nd_ra.rt_lifetime
++#define icmp6_home_agent      icmp6_dataun.u_nd_ra.home_agent
+ };
+diff -uprN linux-2.4.25.old/include/linux/if_arp.h linux-2.4.25/include/linux/if_arp.h
+--- linux-2.4.25.old/include/linux/if_arp.h    2002-02-25 19:38:13.000000000 +0000
++++ linux-2.4.25/include/linux/if_arp.h        2004-06-26 11:29:29.000000000 +0100
+@@ -59,7 +59,7 @@
+ #define ARPHRD_RAWHDLC        518             /* Raw HDLC                     */
+ #define ARPHRD_TUNNEL 768             /* IPIP tunnel                  */
+-#define ARPHRD_TUNNEL6        769             /* IPIP6 tunnel                 */
++#define ARPHRD_TUNNEL6        769             /* IP6IP6 tunnel                */
+ #define ARPHRD_FRAD   770             /* Frame Relay Access Device    */
+ #define ARPHRD_SKIP   771             /* SKIP vif                     */
+ #define ARPHRD_LOOPBACK       772             /* Loopback device              */
+diff -uprN linux-2.4.25.old/include/linux/in6.h linux-2.4.25/include/linux/in6.h
+--- linux-2.4.25.old/include/linux/in6.h       2003-06-13 15:51:38.000000000 +0100
++++ linux-2.4.25/include/linux/in6.h   2004-06-26 11:29:29.000000000 +0100
+@@ -142,6 +142,11 @@ struct in6_flowlabel_req
+ #define IPV6_TLV_JUMBO                194
+ /*
++ *    Mobile IPv6 TLV options.
++ */
++#define MIPV6_TLV_HOMEADDR    201
++
++/*
+  *    IPV6 socket options
+  */
+diff -uprN linux-2.4.25.old/include/linux/ipv6.h linux-2.4.25/include/linux/ipv6.h
+--- linux-2.4.25.old/include/linux/ipv6.h      2003-11-28 18:26:21.000000000 +0000
++++ linux-2.4.25/include/linux/ipv6.h  2004-06-26 11:29:29.000000000 +0100
+@@ -29,6 +29,7 @@ struct in6_ifreq {
+ #define IPV6_SRCRT_STRICT     0x01    /* this hop must be a neighbor  */
+ #define IPV6_SRCRT_TYPE_0     0       /* IPv6 type 0 Routing Header   */
++#define IPV6_SRCRT_TYPE_2     2       /*      type 2 for Mobile IPv6  */
+ /*
+  *    routing header
+@@ -71,6 +72,19 @@ struct rt0_hdr {
+       struct in6_addr         addr[0];
+ #define rt0_type              rt_hdr.type
++
++};
++
++/*
++ *    routing header type 2
++ */
++
++struct rt2_hdr {
++      struct ipv6_rt_hdr      rt_hdr;
++      __u32                   reserved;
++      struct in6_addr         addr;
++
++#define rt2_type              rt_hdr.type;
+ };
+ /*
+@@ -156,12 +170,16 @@ enum {
+ struct inet6_skb_parm
+ {
+       int                     iif;
++      __u8                    mipv6_flags;
+       __u16                   ra;
+       __u16                   hop;
+       __u16                   auth;
+       __u16                   dst0;
+       __u16                   srcrt;
++      __u16                   srcrt2;
++      __u16                   hao;
+       __u16                   dst1;
++      struct in6_addr         hoa;
+ };
+ #endif
+diff -uprN linux-2.4.25.old/include/linux/ipv6_route.h linux-2.4.25/include/linux/ipv6_route.h
+--- linux-2.4.25.old/include/linux/ipv6_route.h        2003-11-28 18:26:21.000000000 +0000
++++ linux-2.4.25/include/linux/ipv6_route.h    2004-06-26 11:29:29.000000000 +0100
+@@ -33,6 +33,7 @@ enum
+ #define RTF_CACHE     0x01000000      /* cache entry                  */
+ #define RTF_FLOW      0x02000000      /* flow significant route       */
+ #define RTF_POLICY    0x04000000      /* policy route                 */
++#define RTF_MOBILENODE  0x10000000      /* for routing to Mobile Node   */
+ #define RTF_LOCAL     0x80000000
+diff -uprN linux-2.4.25.old/include/linux/ipv6_tunnel.h linux-2.4.25/include/linux/ipv6_tunnel.h
+--- linux-2.4.25.old/include/linux/ipv6_tunnel.h       1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/include/linux/ipv6_tunnel.h   2004-06-26 11:29:29.000000000 +0100
+@@ -0,0 +1,34 @@
++/*
++ * $Id$
++ */
++
++#ifndef _IPV6_TUNNEL_H
++#define _IPV6_TUNNEL_H
++
++#define IPV6_TLV_TNL_ENCAP_LIMIT 4
++#define IPV6_DEFAULT_TNL_ENCAP_LIMIT 4
++
++/* don't add encapsulation limit if one isn't present in inner packet */
++#define IP6_TNL_F_IGN_ENCAP_LIMIT 0x1
++/* copy the traffic class field from the inner packet */
++#define IP6_TNL_F_USE_ORIG_TCLASS 0x2
++/* copy the flowlabel from the inner packet */
++#define IP6_TNL_F_USE_ORIG_FLOWLABEL 0x4
++/* created and maintained from within the kernel */
++#define IP6_TNL_F_KERNEL_DEV 0x8
++/* being used for Mobile IPv6 */
++#define IP6_TNL_F_MIP6_DEV 0x10
++
++struct ip6_tnl_parm {
++      char name[IFNAMSIZ];    /* name of tunnel device */
++      int link;               /* ifindex of underlying L2 interface */
++      __u8 proto;             /* tunnel protocol */
++      __u8 encap_limit;       /* encapsulation limit for tunnel */
++      __u8 hop_limit;         /* hop limit for tunnel */
++      __u32 flowinfo;         /* traffic class and flowlabel for tunnel */
++      __u32 flags;            /* tunnel flags */
++      struct in6_addr laddr;  /* local tunnel end-point address */
++      struct in6_addr raddr;  /* remote tunnel end-point address */
++};
++
++#endif
+diff -uprN linux-2.4.25.old/include/linux/rtnetlink.h linux-2.4.25/include/linux/rtnetlink.h
+--- linux-2.4.25.old/include/linux/rtnetlink.h 2004-02-18 13:36:32.000000000 +0000
++++ linux-2.4.25/include/linux/rtnetlink.h     2004-06-26 11:29:29.000000000 +0100
+@@ -309,15 +309,17 @@ enum
+       IFA_LABEL,
+       IFA_BROADCAST,
+       IFA_ANYCAST,
+-      IFA_CACHEINFO
++      IFA_CACHEINFO,
++      IFA_HOMEAGENT
+ };
+-#define IFA_MAX IFA_CACHEINFO
++#define IFA_MAX IFA_HOMEAGENT
+ /* ifa_flags */
+ #define IFA_F_SECONDARY               0x01
++#define IFA_F_HOMEADDR                0x10
+ #define IFA_F_DEPRECATED      0x20
+ #define IFA_F_TENTATIVE               0x40
+ #define IFA_F_PERMANENT               0x80
+diff -uprN linux-2.4.25.old/include/linux/skbuff.h linux-2.4.25/include/linux/skbuff.h
+--- linux-2.4.25.old/include/linux/skbuff.h    2003-08-25 12:44:44.000000000 +0100
++++ linux-2.4.25/include/linux/skbuff.h        2004-06-26 11:29:29.000000000 +0100
+@@ -177,7 +177,7 @@ struct sk_buff {
+        * want to keep them across layers you have to do a skb_clone()
+        * first. This is owned by whoever has the skb queued ATM.
+        */ 
+-      char            cb[48];  
++      char            cb[64];  
+       unsigned int    len;                    /* Length of actual data                        */
+       unsigned int    data_len;
+diff -uprN linux-2.4.25.old/include/linux/sysctl.h linux-2.4.25/include/linux/sysctl.h
+--- linux-2.4.25.old/include/linux/sysctl.h    2004-02-18 13:36:32.000000000 +0000
++++ linux-2.4.25/include/linux/sysctl.h        2004-06-26 11:29:29.000000000 +0100
+@@ -387,7 +387,24 @@ enum {
+       NET_IPV6_NEIGH=17,
+       NET_IPV6_ROUTE=18,
+       NET_IPV6_ICMP=19,
+-      NET_IPV6_BINDV6ONLY=20
++      NET_IPV6_BINDV6ONLY=20,
++      NET_IPV6_MOBILITY=26
++};
++
++/* /proc/sys/net/ipv6/mobility */
++enum {
++      NET_IPV6_MOBILITY_DEBUG=1,
++      NET_IPV6_MOBILITY_TUNNEL_SITELOCAL=2,
++      NET_IPV6_MOBILITY_ROUTER_SOLICITATION_MAX_SENDTIME=3,
++      NET_IPV6_MOBILITY_ROUTER_REACH=4,
++      NET_IPV6_MOBILITY_MDETECT_MECHANISM=5,
++      NET_IPV6_MOBILITY_RETROUT=6,
++      NET_IPV6_MOBILITY_MAX_TNLS=7,
++      NET_IPV6_MOBILITY_MIN_TNLS=8,
++      NET_IPV6_MOBILITY_BINDING_REFRESH=9,
++      NET_IPV6_MOBILITY_BU_F_LLADDR=10,
++      NET_IPV6_MOBILITY_BU_F_KEYMGM=11,
++      NET_IPV6_MOBILITY_BU_F_CN_ACK=12
+ };
+ enum {
+diff -uprN linux-2.4.25.old/include/net/addrconf.h linux-2.4.25/include/net/addrconf.h
+--- linux-2.4.25.old/include/net/addrconf.h    2003-08-25 12:44:44.000000000 +0100
++++ linux-2.4.25/include/net/addrconf.h        2004-06-26 11:29:29.000000000 +0100
+@@ -16,9 +16,11 @@ struct prefix_info {
+ #if defined(__BIG_ENDIAN_BITFIELD)
+       __u8                    onlink : 1,
+                               autoconf : 1,
+-                              reserved : 6;
++                              router_address : 1,
++                              reserved : 5;
+ #elif defined(__LITTLE_ENDIAN_BITFIELD)
+-      __u8                    reserved : 6,
++      __u8                    reserved : 5,
++                              router_address : 1,
+                               autoconf : 1,
+                               onlink : 1;
+ #else
+@@ -55,6 +57,7 @@ extern int                   ipv6_chk_addr(struct in6_ad
+                                             struct net_device *dev);
+ extern struct inet6_ifaddr *  ipv6_get_ifaddr(struct in6_addr *addr,
+                                               struct net_device *dev);
++extern void                   ipv6_del_addr(struct inet6_ifaddr *ifp);
+ extern int                    ipv6_get_saddr(struct dst_entry *dst, 
+                                              struct in6_addr *daddr,
+                                              struct in6_addr *saddr);
+@@ -85,7 +88,9 @@ extern void ipv6_mc_up(struct inet6_dev 
+ extern void ipv6_mc_down(struct inet6_dev *idev);
+ extern void ipv6_mc_init_dev(struct inet6_dev *idev);
+ extern void ipv6_mc_destroy_dev(struct inet6_dev *idev);
++extern void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags);
+ extern void addrconf_dad_failure(struct inet6_ifaddr *ifp);
++extern void addrconf_dad_completed(struct inet6_ifaddr *ifp);
+ extern int ipv6_chk_mcast_addr(struct net_device *dev, struct in6_addr *group,
+               struct in6_addr *src_addr);
+@@ -116,6 +121,9 @@ extern int                 ipv6_chk_acast_addr(struct 
+ extern int register_inet6addr_notifier(struct notifier_block *nb);
+ extern int unregister_inet6addr_notifier(struct notifier_block *nb);
++extern int ipv6_generate_eui64(u8 *eui, struct net_device *dev);
++extern int ipv6_inherit_eui64(u8 *eui, struct inet6_dev *idev);
++
+ static inline struct inet6_dev *
+ __in6_dev_get(struct net_device *dev)
+ {
+diff -uprN linux-2.4.25.old/include/net/ip6_route.h linux-2.4.25/include/net/ip6_route.h
+--- linux-2.4.25.old/include/net/ip6_route.h   2003-06-13 15:51:39.000000000 +0100
++++ linux-2.4.25/include/net/ip6_route.h       2004-06-26 11:29:29.000000000 +0100
+@@ -2,6 +2,7 @@
+ #define _NET_IP6_ROUTE_H
+ #define IP6_RT_PRIO_FW                16
++#define IP6_RT_PRIO_MIPV6             64
+ #define IP6_RT_PRIO_USER      1024
+ #define IP6_RT_PRIO_ADDRCONF  256
+ #define IP6_RT_PRIO_KERN      512
+@@ -40,6 +41,9 @@ extern int                   ipv6_route_ioctl(unsigned i
+ extern int                    ip6_route_add(struct in6_rtmsg *rtmsg,
+                                             struct nlmsghdr *);
++
++extern int                    ip6_route_del(struct in6_rtmsg *rtmsg,
++                                            struct nlmsghdr *);
+ extern int                    ip6_del_rt(struct rt6_info *,
+                                          struct nlmsghdr *);
+@@ -99,7 +103,8 @@ extern rwlock_t rt6_lock;
+  */
+ static inline void ip6_dst_store(struct sock *sk, struct dst_entry *dst,
+-                                   struct in6_addr *daddr)
++                               struct in6_addr *daddr, 
++                               struct in6_addr *saddr)
+ {
+       struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+       struct rt6_info *rt = (struct rt6_info *) dst;
+@@ -107,6 +112,9 @@ static inline void ip6_dst_store(struct 
+       write_lock(&sk->dst_lock);
+       __sk_dst_set(sk, dst);
+       np->daddr_cache = daddr;
++#ifdef CONFIG_IPV6_SUBTREES
++      np->saddr_cache = saddr;
++#endif
+       np->dst_cookie = rt->rt6i_node ? rt->rt6i_node->fn_sernum : 0;
+       write_unlock(&sk->dst_lock);
+ }
+diff -uprN linux-2.4.25.old/include/net/ipv6.h linux-2.4.25/include/net/ipv6.h
+--- linux-2.4.25.old/include/net/ipv6.h        2003-11-28 18:26:21.000000000 +0000
++++ linux-2.4.25/include/net/ipv6.h    2004-06-26 11:29:29.000000000 +0100
+@@ -37,6 +37,7 @@
+ #define NEXTHDR_ICMP          58      /* ICMP for IPv6. */
+ #define NEXTHDR_NONE          59      /* No next header */
+ #define NEXTHDR_DEST          60      /* Destination options header. */
++#define NEXTHDR_MH            135     /* Mobility header, RFC 3775 */
+ #define NEXTHDR_MAX           255
+@@ -145,9 +146,12 @@ struct ipv6_txoptions
+       __u16                   opt_flen;       /* after fragment hdr */
+       __u16                   opt_nflen;      /* before fragment hdr */
++      __u8                    mipv6_flags;    /* flags set by MIPv6 */
++
+       struct ipv6_opt_hdr     *hopopt;
+       struct ipv6_opt_hdr     *dst0opt;
+-      struct ipv6_rt_hdr      *srcrt; /* Routing Header */
++      struct ipv6_rt_hdr      *srcrt; /* Routing Header Type 0 */
++      struct ipv6_rt_hdr      *srcrt2; /* Routing Header Type 2 */
+       struct ipv6_opt_hdr     *auth;
+       struct ipv6_opt_hdr     *dst1opt;
+@@ -256,6 +260,38 @@ static inline int ipv6_addr_any(const st
+                a->s6_addr32[2] | a->s6_addr32[3] ) == 0); 
+ }
++static inline void ipv6_addr_prefix(struct in6_addr *pfx,
++                                  const struct in6_addr *addr, int plen)
++{
++      /* caller must guarantee 0 <= plen <= 128 */
++      int o = plen >> 3,
++          b = plen & 0x7;
++
++      memcpy(pfx->s6_addr, addr, o);
++      if (b != 0) {
++              pfx->s6_addr[o] = addr->s6_addr[o] & (0xff00 >> b);
++              o++;
++      }
++      if (o < 16)
++              memset(pfx->s6_addr + o, 0, 16 - o);
++}
++
++static inline int ipv6_prefix_cmp(const struct in6_addr *p1,
++                                const struct in6_addr *p2, int plen)
++{
++      int b = plen&0x7;
++      int o = plen>>3;
++      int res = 0;
++
++      if (o > 0) 
++              res = memcmp(&p1->s6_addr[0], &p2->s6_addr[0], o);
++      if (res == 0 && b > 0) {
++              __u8 m = (0xff00 >> b) & 0xff;
++              res = (p1->s6_addr[o] & m) - (p2->s6_addr[o] & m);  
++      }
++      return res;
++}
++
+ /*
+  *    Prototypes exported by ipv6
+  */
+diff -uprN linux-2.4.25.old/include/net/ipv6_tunnel.h linux-2.4.25/include/net/ipv6_tunnel.h
+--- linux-2.4.25.old/include/net/ipv6_tunnel.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/include/net/ipv6_tunnel.h     2004-06-26 11:29:29.000000000 +0100
+@@ -0,0 +1,92 @@
++/*
++ * $Id$
++ */
++
++#ifndef _NET_IPV6_TUNNEL_H
++#define _NET_IPV6_TUNNEL_H
++
++#include <linux/ipv6.h>
++#include <linux/netdevice.h>
++#include <linux/ipv6_tunnel.h>
++#include <linux/skbuff.h>
++#include <asm/atomic.h>
++
++/* capable of sending packets */
++#define IP6_TNL_F_CAP_XMIT 0x10000
++/* capable of receiving packets */
++#define IP6_TNL_F_CAP_RCV 0x20000
++
++#define IP6_TNL_MAX 128
++
++/* IPv6 tunnel */
++
++struct ip6_tnl {
++      struct ip6_tnl *next;   /* next tunnel in list */
++      struct net_device *dev; /* virtual device associated with tunnel */
++      struct net_device_stats stat;   /* statistics for tunnel device */
++      int recursion;          /* depth of hard_start_xmit recursion */
++      struct ip6_tnl_parm parms;      /* tunnel configuration paramters */
++      struct flowi fl;        /* flowi template for xmit */
++      atomic_t refcnt;        /* nr of identical tunnels used by kernel */
++      struct socket *sock;
++};
++
++#define IP6_TNL_PRE_ENCAP 0
++#define IP6_TNL_PRE_DECAP 1
++#define IP6_TNL_MAXHOOKS 2
++
++#define IP6_TNL_DROP 0
++#define IP6_TNL_ACCEPT 1
++
++typedef int ip6_tnl_hookfn(struct ip6_tnl *t, struct sk_buff *skb);
++
++struct ip6_tnl_hook_ops {
++      struct list_head list;
++      unsigned int hooknum;
++      int priority;
++      ip6_tnl_hookfn *hook;
++};
++
++enum ip6_tnl_hook_priorities {
++      IP6_TNL_PRI_FIRST = INT_MIN,
++      IP6_TNL_PRI_LAST = INT_MAX
++};
++
++/* Tunnel encapsulation limit destination sub-option */
++
++struct ipv6_tlv_tnl_enc_lim {
++      __u8 type;              /* type-code for option         */
++      __u8 length;            /* option length                */
++      __u8 encap_limit;       /* tunnel encapsulation limit   */
++} __attribute__ ((packed));
++
++#ifdef __KERNEL__
++extern int ip6ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt);
++
++extern struct ip6_tnl *ip6ip6_tnl_lookup(struct in6_addr *remote,
++                                       struct in6_addr *local);
++
++void ip6ip6_tnl_change(struct ip6_tnl *t, struct ip6_tnl_parm *p);
++
++extern int ip6ip6_kernel_tnl_add(struct ip6_tnl_parm *p);
++
++extern int ip6ip6_kernel_tnl_del(struct ip6_tnl *t);
++
++extern unsigned int ip6ip6_tnl_inc_max_kdev_count(unsigned int n);
++
++extern unsigned int ip6ip6_tnl_dec_max_kdev_count(unsigned int n);
++
++extern unsigned int ip6ip6_tnl_inc_min_kdev_count(unsigned int n);
++
++extern unsigned int ip6ip6_tnl_dec_min_kdev_count(unsigned int n);
++
++extern void ip6ip6_tnl_register_hook(struct ip6_tnl_hook_ops *reg);
++
++extern void ip6ip6_tnl_unregister_hook(struct ip6_tnl_hook_ops *reg);
++
++#ifdef CONFIG_IPV6_TUNNEL
++extern int __init ip6_tunnel_init(void);
++extern void ip6_tunnel_cleanup(void);
++#endif
++#endif
++#endif
+diff -uprN linux-2.4.25.old/include/net/mipglue.h linux-2.4.25/include/net/mipglue.h
+--- linux-2.4.25.old/include/net/mipglue.h     1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/include/net/mipglue.h 2004-06-26 11:29:29.000000000 +0100
+@@ -0,0 +1,266 @@
++/*
++ *    Glue for Mobility support integration to IPv6
++ *
++ *    Authors:
++ *    Antti Tuominen          <ajtuomin@cc.hut.fi>    
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ *
++ */
++
++#ifndef _NET_MIPGLUE_H
++#define _NET_MIPGLUE_H
++
++#ifndef USE_IPV6_MOBILITY
++#if defined(CONFIG_IPV6_MOBILITY) || defined(CONFIG_IPV6_MOBILITY_MODULE)
++#define USE_IPV6_MOBILITY
++#endif
++#endif
++
++/* symbols to indicate whether destination options received should take
++ * effect or not (see exthdrs.c, procrcv.c)
++ */
++#define MIPV6_DSTOPTS_ACCEPT 1
++#define MIPV6_DSTOPTS_DISCARD 0
++
++#define MIPV6_IGN_RTR 0
++#define MIPV6_ADD_RTR 1
++#define MIPV6_CHG_RTR 2
++
++/* MIPV6: Approximate maximum for mobile IPv6 options and headers */
++#define MIPV6_HEADERS 48
++
++#ifdef __KERNEL__
++#include <net/mipv6.h>
++#include <linux/slab.h>
++#include <net/ipv6.h>
++
++struct sk_buff;
++struct ndisc_options;
++struct sock;
++struct ipv6_txoptions;
++struct flowi;
++struct dst_entry;
++struct in6_addr;
++struct inet6_ifaddr;
++
++#ifdef USE_IPV6_MOBILITY
++
++/* calls a procedure from mipv6-module */
++#define MIPV6_CALLPROC(X) if(mipv6_functions.X) mipv6_functions.X
++
++/* calls a function from mipv6-module, default-value if function not defined
++ */
++#define MIPV6_CALLFUNC(X,Y) (!mipv6_functions.X)?(Y):mipv6_functions.X
++
++/* sets a handler-function to process a call */
++#define MIPV6_SETCALL(X,Y) if(mipv6_functions.X) printk("mipv6: Warning, function assigned twice!\n"); \
++                           mipv6_functions.X = Y
++#define MIPV6_RESETCALL(X) mipv6_functions.X = NULL
++
++/* pointers to mipv6 callable functions */
++struct mipv6_callable_functions {
++      void (*mipv6_initialize_dstopt_rcv) (struct sk_buff *skb);
++      int (*mipv6_finalize_dstopt_rcv) (int process);
++      int (*mipv6_handle_homeaddr) (struct sk_buff *skb, int optoff);
++      int (*mipv6_ra_rcv) (struct sk_buff *skb,
++                           struct ndisc_options *ndopts);
++      void (*mipv6_icmp_rcv) (struct sk_buff *skb);
++      struct ipv6_txoptions * (*mipv6_modify_txoptions) (
++              struct sock *sk,
++              struct sk_buff *skb, 
++              struct ipv6_txoptions *opt,
++              struct flowi *fl,
++              struct dst_entry **dst);
++      void (*mipv6_set_home) (int ifindex, struct in6_addr *homeaddr,
++                              int plen, struct in6_addr *homeagent, 
++                              int plen2);
++      void (*mipv6_get_home_address) (struct in6_addr *home_addr);
++      void (*mipv6_get_care_of_address)(struct in6_addr *homeaddr, 
++                                        struct in6_addr *coa);
++      int (*mipv6_is_home_addr)(struct in6_addr *addr);
++      void (*mipv6_change_router)(void);
++      void (*mipv6_check_dad)(struct in6_addr *home_addr);      
++      void (*mipv6_icmp_swap_addrs)(struct sk_buff *skb);
++      int (*mipv6_forward)(struct sk_buff *skb);
++      int (*mipv6_mn_ha_probe)(struct inet6_ifaddr *ifp, u8 *lladdr);
++};
++
++extern struct mipv6_callable_functions mipv6_functions;
++
++extern void mipv6_invalidate_calls(void);
++
++extern int mipv6_handle_dstopt(struct sk_buff *skb, int optoff);
++
++static inline int
++ndisc_mip_mn_ha_probe(struct inet6_ifaddr *ifp, u8 *lladdr)
++{
++      return MIPV6_CALLFUNC(mipv6_mn_ha_probe, 0)(ifp, lladdr);
++}
++
++/* Must only be called for HA, no checks here */
++static inline int ip6_mipv6_forward(struct sk_buff *skb)
++{
++      return MIPV6_CALLFUNC(mipv6_forward, 0)(skb);
++}
++
++/* 
++ * Avoid adding new default routers if the old one is still in use 
++ */
++
++static inline int ndisc_mipv6_ra_rcv(struct sk_buff *skb,
++                                   struct ndisc_options *ndopts)
++{
++      return MIPV6_CALLFUNC(mipv6_ra_rcv, MIPV6_ADD_RTR)(skb, ndopts);
++}
++
++static inline int ipv6_chk_mip_home_addr(struct in6_addr *addr)
++{
++      return MIPV6_CALLFUNC(mipv6_is_home_addr, 0)(addr);
++}
++
++static inline void ndisc_mipv6_change_router(int change_rtr)
++{
++      if (change_rtr == MIPV6_CHG_RTR)
++              MIPV6_CALLPROC(mipv6_change_router)();
++}
++
++static inline void ndisc_check_mipv6_dad(struct in6_addr *target)
++{
++      MIPV6_CALLPROC(mipv6_check_dad)(target);
++}
++
++static inline void icmpv6_swap_mipv6_addrs(struct sk_buff *skb)
++{
++      MIPV6_CALLPROC(mipv6_icmp_swap_addrs)(skb);
++}
++
++static inline void mipv6_icmp_rcv(struct sk_buff *skb)
++{
++      MIPV6_CALLPROC(mipv6_icmp_rcv)(skb);
++}
++
++static inline int tcp_v6_get_mipv6_header_len(void)
++{
++      return MIPV6_HEADERS;
++}
++
++static inline struct in6_addr *
++mipv6_get_fake_hdr_daddr(struct in6_addr *hdaddr, struct in6_addr *daddr)
++{
++      return daddr;
++}
++
++static inline void 
++addrconf_set_mipv6_mn_home(int ifindex, struct in6_addr *homeaddr, int plen,
++                         struct in6_addr *homeagent, int plen2)
++{
++      MIPV6_CALLPROC(mipv6_set_home)(ifindex, homeaddr, plen, homeagent, plen2);
++}
++
++static inline void addrconf_get_mipv6_home_address(struct in6_addr *saddr)
++{
++      MIPV6_CALLPROC(mipv6_get_home_address)(saddr);
++}
++
++static inline struct ipv6_txoptions *
++ip6_add_mipv6_txoptions(struct sock *sk, struct sk_buff *skb, 
++                      struct ipv6_txoptions *opt, struct flowi *fl,
++                      struct dst_entry **dst)
++{
++      return MIPV6_CALLFUNC(mipv6_modify_txoptions, opt)(sk, skb, opt, fl, dst); 
++
++}
++
++static inline void
++ip6_mark_mipv6_packet(struct ipv6_txoptions *txopt, struct sk_buff *skb)
++{
++      struct inet6_skb_parm *opt;
++      if (txopt) {
++              opt = (struct inet6_skb_parm *)skb->cb;
++              opt->mipv6_flags = txopt->mipv6_flags;
++      }
++}
++
++static inline void 
++ip6_free_mipv6_txoptions(struct ipv6_txoptions *opt,
++                       struct ipv6_txoptions *orig_opt) 
++{
++      if (opt && opt != orig_opt)
++              kfree(opt);
++}
++
++#else /* USE_IPV6_MOBILITY */
++
++#define mipv6_handle_dstopt ip6_tlvopt_unknown
++
++static inline int
++ndisc_mip_mn_ha_probe(struct inet6_ifaddr *ifp, u8 *lladdr)
++{
++      return 0;
++}
++
++static inline int ip6_mipv6_forward(struct sk_buff *skb)
++{
++      return 0;
++}
++
++static inline int ndisc_mipv6_ra_rcv(struct sk_buff *skb,
++                                   struct ndisc_options *ndopts)
++{
++      return MIPV6_ADD_RTR;
++}
++
++static inline int ipv6_chk_mip_home_addr(struct in6_addr *addr)
++{
++      return 0;
++}
++
++static inline void ndisc_mipv6_change_router(int change_rtr) {}
++
++static inline void ndisc_check_mipv6_dad(struct in6_addr *target) {}
++
++static inline void icmpv6_swap_mipv6_addrs(struct sk_buff *skb) {}
++
++static inline void mipv6_icmp_rcv(struct sk_buff *skb) {}
++
++static inline int tcp_v6_get_mipv6_header_len(void)
++{
++      return 0;
++}
++
++static inline struct in6_addr *
++mipv6_get_fake_hdr_daddr(struct in6_addr *hdaddr, struct in6_addr *daddr)
++{
++      return hdaddr;
++}
++
++static inline void 
++addrconf_set_mipv6_mn_home(int ifindex, struct in6_addr *homeaddr, int plen,
++                         struct in6_addr *homeagent, int plen2) {}
++
++static inline void addrconf_get_mipv6_home_address(struct in6_addr *saddr) {}
++
++static inline struct ipv6_txoptions *
++ip6_add_mipv6_txoptions(struct sock *sk, struct sk_buff *skb,
++                      struct ipv6_txoptions *opt, struct flowi *fl,
++                      struct dst_entry **dst)
++{
++      return opt;
++}
++
++static inline void
++ip6_mark_mipv6_packet(struct ipv6_txoptions *txopt, struct sk_buff *skb) {}
++
++static inline void 
++ip6_free_mipv6_txoptions(struct ipv6_txoptions *opt, 
++                       struct ipv6_txoptions *orig_opt) {}
++
++#endif /* USE_IPV6_MOBILITY */
++#endif /* __KERNEL__ */
++#endif /* _NET_MIPGLUE_H */
+diff -uprN linux-2.4.25.old/include/net/mipv6.h linux-2.4.25/include/net/mipv6.h
+--- linux-2.4.25.old/include/net/mipv6.h       1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/include/net/mipv6.h   2004-06-26 11:29:29.000000000 +0100
+@@ -0,0 +1,258 @@
++/*
++ *    Mobile IPv6 header-file
++ *
++ *    Authors:
++ *    Sami Kivisaari          <skivisaa@cc.hut.fi>
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ *
++ */
++
++#ifndef _NET_MIPV6_H
++#define _NET_MIPV6_H
++
++#include <linux/types.h>
++#include <asm/byteorder.h>
++#include <linux/in6.h>
++
++/*
++ *
++ * Mobile IPv6 Protocol constants
++ *
++ */
++#define DHAAD_RETRIES                 4       /* transmissions        */
++#define INITIAL_BINDACK_TIMEOUT               1       /* seconds              */
++#define INITIAL_DHAAD_TIMEOUT         3       /* seconds              */
++#define INITIAL_SOLICIT_TIMER         3       /* seconds              */
++#define MAX_BINDACK_TIMEOUT           32      /* seconds              */
++#define MAX_NONCE_LIFE                        240     /* seconds              */
++#define MAX_TOKEN_LIFE                        210     /* seconds              */
++#define MAX_RR_BINDING_LIFE           420     /* seconds              */
++#define MAX_UPDATE_RATE                       3       /* 1/s (min delay=1s)   */
++#define PREFIX_ADV_RETRIES            3       /* transmissions        */
++#define PREFIX_ADV_TIMEOUT            3       /* seconds              */
++
++#define MAX_FAST_UPDATES              5       /* transmissions        */
++#define MAX_PFX_ADV_DELAY             1000    /* seconds              */
++#define SLOW_UPDATE_RATE              10      /* 1/10s (max delay=10s)*/
++#define INITIAL_BINDACK_DAD_TIMEOUT   2       /* seconds              */
++
++/*
++ *
++ * Mobile IPv6 (RFC 3775) Protocol configuration variable defaults
++ *
++ */
++#define DefHomeRtrAdvInterval         1000    /* seconds              */
++#define DefMaxMobPfxAdvInterval               86400   /* seconds              */
++#define DefMinDelayBetweenRAs         3       /* seconds (min 0.03)   */
++#define DefMinMobPfxAdvInterval               600     /* seconds              */
++#define DefInitialBindackTimeoutFirstReg      1.5 /* seconds          */
++
++/* This is not actually specified in the draft, but is needed to avoid
++ * prefix solicitation storm when valid lifetime of a prefix is smaller
++ * than MAX_PFX_ADV_DELAY
++ */
++#define MIN_PFX_SOL_DELAY             5       /* seconds              */
++
++/* Mobile IPv6 ICMP types               */
++/*
++ * Official numbers from RFC 3775
++ */
++#define MIPV6_DHAAD_REQUEST           144
++#define MIPV6_DHAAD_REPLY             145
++#define MIPV6_PREFIX_SOLICIT          146
++#define MIPV6_PREFIX_ADV              147
++
++/* Binding update flag codes              */
++#define MIPV6_BU_F_ACK                        0x80
++#define MIPV6_BU_F_HOME                       0x40
++#define MIPV6_BU_F_LLADDR             0x20
++#define MIPV6_BU_F_KEYMGM             0x10
++
++/* Binding ackknowledgment flag codes */
++#define MIPV6_BA_F_KEYMGM             0x80
++
++/* Binding error status */
++#define MIPV6_BE_HAO_WO_BINDING               1
++#define MIPV6_BE_UNKNOWN_MH_TYPE      2
++
++/* Mobility Header */
++struct mipv6_mh
++{
++      __u8    payload;                /* Payload Protocol             */
++      __u8    length;                 /* MH Length                    */
++      __u8    type;                   /* MH Type                      */
++      __u8    reserved;               /* Reserved                     */
++      __u16   checksum;               /* Checksum                     */
++      __u8    data[0];                /* Message specific data        */
++} __attribute__ ((packed));
++
++/* Mobility Header type */
++#define IPPROTO_MOBILITY                135 /* RFC 3775*/                
++/* Mobility Header Message Types */
++
++#define MIPV6_MH_BRR                  0
++#define MIPV6_MH_HOTI                 1
++#define MIPV6_MH_COTI                 2
++#define MIPV6_MH_HOT                  3
++#define MIPV6_MH_COT                  4
++#define MIPV6_MH_BU                   5
++#define MIPV6_MH_BA                   6
++#define MIPV6_MH_BE                   7
++
++/*
++ * Status codes for Binding Acknowledgements
++ */
++#define SUCCESS                               0
++#define REASON_UNSPECIFIED            128
++#define ADMINISTRATIVELY_PROHIBITED   129
++#define INSUFFICIENT_RESOURCES                130
++#define HOME_REGISTRATION_NOT_SUPPORTED       131
++#define NOT_HOME_SUBNET                       132
++#define NOT_HA_FOR_MN                 133
++#define DUPLICATE_ADDR_DETECT_FAIL    134
++#define SEQUENCE_NUMBER_OUT_OF_WINDOW 135
++#define EXPIRED_HOME_NONCE_INDEX      136
++#define EXPIRED_CAREOF_NONCE_INDEX    137
++#define EXPIRED_NONCES                        138
++#define REG_TYPE_CHANGE_FORBIDDEN       139
++/*
++ * Values for mipv6_flags in struct inet6_skb_parm
++ */
++
++#define MIPV6_RCV_TUNNEL              0x1
++#define MIPV6_SND_HAO                 0x2
++#define MIPV6_SND_BU                    0x4
++
++/*
++ * Mobility Header Message structures
++ */
++
++struct mipv6_mh_brr
++{
++      __u16           reserved;
++      /* Mobility options */
++} __attribute__ ((packed));
++
++struct mipv6_mh_bu
++{
++      __u16           sequence;       /* sequence number of BU        */
++      __u8            flags;          /* flags                        */
++      __u8            reserved;       /* reserved bits                */
++      __u16           lifetime;       /* lifetime of BU               */
++      /* Mobility options */
++} __attribute__ ((packed));
++
++struct mipv6_mh_ba
++{
++      __u8            status;         /* statuscode                   */
++      __u8            reserved;       /* reserved bits                */
++      __u16           sequence;       /* sequence number of BA        */
++      __u16           lifetime;       /* lifetime in CN's bcache      */
++      /* Mobility options */
++} __attribute__ ((packed));
++
++struct mipv6_mh_be
++{
++      __u8            status;
++      __u8            reserved;
++      struct in6_addr home_addr;
++      /* Mobility options */
++} __attribute__ ((packed));
++
++struct mipv6_mh_addr_ti
++{
++      __u16           reserved;       /* Reserved                     */
++      u_int8_t        init_cookie[8]; /* HoT/CoT Init Cookie          */
++      /* Mobility options */
++} __attribute__ ((packed));
++
++struct mipv6_mh_addr_test
++{
++      __u16           nonce_index;    /* Home/Care-of Nonce Index     */
++      u_int8_t        init_cookie[8]; /* HoT/CoT Init Cookie          */
++      u_int8_t        kgen_token[8];  /* Home/Care-of key generation token */
++      /* Mobility options */
++} __attribute__ ((packed));
++
++/*
++ * Mobility Options for various MH types.
++ */
++#define MIPV6_OPT_PAD1                        0x00
++#define MIPV6_OPT_PADN                        0x01
++#define MIPV6_OPT_BIND_REFRESH_ADVICE 0x02
++#define MIPV6_OPT_ALTERNATE_COA               0x03
++#define MIPV6_OPT_NONCE_INDICES               0x04
++#define MIPV6_OPT_AUTH_DATA           0x05
++
++#define MIPV6_SEQ_GT(x,y) \
++        ((short int)(((__u16)(x)) - ((__u16)(y))) > 0)
++
++/*
++ * Mobility Option structures
++ */
++
++struct mipv6_mo
++{
++      __u8            type;
++      __u8            length;
++      __u8            value[0];       /* type specific data */
++} __attribute__ ((packed));
++
++struct mipv6_mo_pad1
++{
++      __u8            type;
++} __attribute__ ((packed));
++
++struct mipv6_mo_padn
++{
++      __u8            type;
++      __u8            length;
++      __u8            data[0];
++} __attribute__ ((packed));
++
++struct mipv6_mo_alt_coa
++{
++      __u8            type;
++      __u8            length;
++      struct in6_addr addr;           /* alternate care-of-address    */
++} __attribute__ ((packed));
++
++struct mipv6_mo_nonce_indices
++{
++      __u8            type;
++      __u8            length;
++      __u16           home_nonce_i;   /* Home Nonce Index             */
++      __u16           careof_nonce_i; /* Careof Nonce Index           */
++} __attribute__ ((packed)); 
++
++struct mipv6_mo_bauth_data
++{
++      __u8            type;
++      __u8            length;
++      __u8            data[0];
++} __attribute__ ((packed)); 
++
++struct mipv6_mo_br_advice
++{
++      __u8            type;
++      __u8            length;
++      __u16           refresh_interval; /* Refresh Interval           */
++} __attribute__ ((packed));
++
++/*
++ * Home Address Destination Option structure
++ */
++struct mipv6_dstopt_homeaddr
++{
++      __u8            type;           /* type-code for option         */
++      __u8            length;         /* option length                */
++      struct in6_addr addr;           /* home address                 */
++} __attribute__ ((packed));
++
++#endif /* _NET_MIPV6_H */
+diff -uprN linux-2.4.25.old/include/net/ndisc.h linux-2.4.25/include/net/ndisc.h
+--- linux-2.4.25.old/include/net/ndisc.h       2002-11-28 23:53:15.000000000 +0000
++++ linux-2.4.25/include/net/ndisc.h   2004-06-26 11:29:29.000000000 +0100
+@@ -21,6 +21,10 @@
+ #define ND_OPT_REDIRECT_HDR           4
+ #define ND_OPT_MTU                    5
++/* Mobile IPv6 specific ndisc options */ 
++#define ND_OPT_RTR_ADV_INTERVAL               7 
++#define ND_OPT_HOME_AGENT_INFO                8  
++
+ #define MAX_RTR_SOLICITATION_DELAY    HZ
+ #define ND_REACHABLE_TIME             (30*HZ)
+@@ -57,7 +61,7 @@ struct nd_opt_hdr {
+ } __attribute__((__packed__));
+ struct ndisc_options {
+-      struct nd_opt_hdr *nd_opt_array[7];
++      struct nd_opt_hdr *nd_opt_array[10];
+       struct nd_opt_hdr *nd_opt_piend;
+ };
+@@ -67,6 +71,8 @@ struct ndisc_options {
+ #define nd_opts_pi_end                nd_opt_piend
+ #define nd_opts_rh            nd_opt_array[ND_OPT_REDIRECT_HDR]
+ #define nd_opts_mtu           nd_opt_array[ND_OPT_MTU]
++#define nd_opts_rai           nd_opt_array[ND_OPT_RTR_ADV_INTERVAL]
++#define nd_opts_hai           nd_opt_array[ND_OPT_HOME_AGENT_INFO]
+ extern struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur, struct nd_opt_hdr *end);
+ extern struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, struct ndisc_options *ndopts);
+@@ -83,6 +89,15 @@ extern void                 ndisc_send_ns(struct net_d
+                                             struct in6_addr *daddr,
+                                             struct in6_addr *saddr);
++extern void                   ndisc_send_na(struct net_device *dev,
++                                            struct neighbour *neigh,
++                                            struct in6_addr *daddr,
++                                            struct in6_addr *solicited_addr,
++                                            int router,
++                                            int solicited,
++                                            int override,
++                                            int inc_opt);
++
+ extern void                   ndisc_send_rs(struct net_device *dev,
+                                             struct in6_addr *saddr,
+                                             struct in6_addr *daddr);
+diff -uprN linux-2.4.25.old/include/net/sock.h linux-2.4.25/include/net/sock.h
+--- linux-2.4.25.old/include/net/sock.h        2004-02-18 13:36:32.000000000 +0000
++++ linux-2.4.25/include/net/sock.h    2004-06-26 11:29:30.000000000 +0100
+@@ -149,7 +149,9 @@ struct ipv6_pinfo {
+       struct in6_addr         rcv_saddr;
+       struct in6_addr         daddr;
+       struct in6_addr         *daddr_cache;
+-
++#if defined(CONFIG_IPV6_SUBTREES)
++      struct in6_addr         *saddr_cache;
++#endif
+       __u32                   flow_label;
+       __u32                   frag_size;
+       int                     hop_limit;
+diff -uprN linux-2.4.25.old/net/Makefile linux-2.4.25/net/Makefile
+--- linux-2.4.25.old/net/Makefile      2004-06-26 11:22:00.000000000 +0100
++++ linux-2.4.25/net/Makefile  2004-06-26 11:29:30.000000000 +0100
+@@ -7,7 +7,7 @@
+ O_TARGET :=   network.o
+-mod-subdirs :=        ipv4/netfilter ipv6/netfilter ipx irda bluetooth atm netlink sched core sctp
++mod-subdirs :=        ipv4/netfilter ipv6/netfilter ipx irda bluetooth atm netlink sched core sctp ipv6
+ export-objs :=        netsyms.o
+ subdir-y :=   core ethernet
+@@ -25,6 +25,7 @@ subdir-$(CONFIG_IP_SCTP)     += sctp
+ ifneq ($(CONFIG_IPV6),n)
+ ifneq ($(CONFIG_IPV6),)
+ subdir-$(CONFIG_NETFILTER)    += ipv6/netfilter
++subdir-$(CONFIG_IPV6_MOBILITY)        += ipv6/mobile_ip6
+ endif
+ endif
+diff -uprN linux-2.4.25.old/net/core/neighbour.c linux-2.4.25/net/core/neighbour.c
+--- linux-2.4.25.old/net/core/neighbour.c      2004-02-18 13:36:32.000000000 +0000
++++ linux-2.4.25/net/core/neighbour.c  2004-06-26 11:29:30.000000000 +0100
+@@ -386,7 +386,7 @@ struct pneigh_entry * pneigh_lookup(stru
+       if (!creat)
+               return NULL;
+-      n = kmalloc(sizeof(*n) + key_len, GFP_KERNEL);
++      n = kmalloc(sizeof(*n) + key_len, GFP_ATOMIC);
+       if (n == NULL)
+               return NULL;
+diff -uprN linux-2.4.25.old/net/ipv6/Config.in linux-2.4.25/net/ipv6/Config.in
+--- linux-2.4.25.old/net/ipv6/Config.in        2001-12-21 17:42:05.000000000 +0000
++++ linux-2.4.25/net/ipv6/Config.in    2004-06-26 11:29:30.000000000 +0100
+@@ -1,10 +1,16 @@
+ #
+ # IPv6 configuration
+ # 
+-
++bool '    IPv6: routing by source address (EXPERIMENTAL)' CONFIG_IPV6_SUBTREES
+ #bool '    IPv6: flow policy support' CONFIG_RT6_POLICY
+ #bool '    IPv6: firewall support' CONFIG_IPV6_FIREWALL
++if [ "$CONFIG_IPV6" != "n" ]; then
++      dep_tristate '    IPv6: IPv6 over IPv6 Tunneling (EXPERIMENTAL)' CONFIG_IPV6_TUNNEL $CONFIG_IPV6
++fi
++
++source net/ipv6/mobile_ip6/Config.in
++
+ if [ "$CONFIG_NETFILTER" != "n" ]; then
+    source net/ipv6/netfilter/Config.in
+ fi
+diff -uprN linux-2.4.25.old/net/ipv6/Makefile linux-2.4.25/net/ipv6/Makefile
+--- linux-2.4.25.old/net/ipv6/Makefile 2003-11-28 18:26:21.000000000 +0000
++++ linux-2.4.25/net/ipv6/Makefile     2004-06-26 11:29:30.000000000 +0100
+@@ -6,18 +6,28 @@
+ # unless it's something special (ie not a .c file).
+ #
++export-objs := ipv6_syms.o ipv6_tunnel.o
+-O_TARGET := ipv6.o
++#list-multi   := ipv6.o ipv6_tunnel.o
+-obj-y :=      af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o sit.o \
+-              route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o raw.o \
+-              protocol.o icmp.o mcast.o reassembly.o tcp_ipv6.o \
+-              exthdrs.o sysctl_net_ipv6.o datagram.o proc.o \
+-              ip6_flowlabel.o ipv6_syms.o
++ipv6-objs     := af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o \
++                 sit.o route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o \
++                 raw.o protocol.o icmp.o mcast.o reassembly.o tcp_ipv6.o \
++                 exthdrs.o sysctl_net_ipv6.o datagram.o proc.o \
++                 ip6_flowlabel.o ipv6_syms.o
++
++ifneq ($(CONFIG_IPV6_MOBILITY),n)
++ifneq ($(CONFIG_IPV6_MOBILITY),)
++ipv6-objs += mipglue.o
++endif
++endif
+-export-objs := ipv6_syms.o
++obj-$(CONFIG_IPV6)  += ipv6.o
++obj-$(CONFIG_IPV6_TUNNEL)  += ipv6_tunnel.o
++
++ipv6.o: $(ipv6-objs)
++      $(LD) -r -o $@ $(ipv6-objs)
+-obj-m  := $(O_TARGET)
+ #obj-$(CONFIG_IPV6_FIREWALL) += ip6_fw.o
+diff -uprN linux-2.4.25.old/net/ipv6/addrconf.c linux-2.4.25/net/ipv6/addrconf.c
+--- linux-2.4.25.old/net/ipv6/addrconf.c       2003-11-28 18:26:21.000000000 +0000
++++ linux-2.4.25/net/ipv6/addrconf.c   2004-06-26 11:29:30.000000000 +0100
+@@ -68,6 +68,8 @@
+ #include <asm/uaccess.h>
++#include <net/mipglue.h>
++
+ #define IPV6_MAX_ADDRESSES 16
+ /* Set to 3 to get tracing... */
+@@ -103,9 +105,9 @@ static spinlock_t addrconf_verify_lock =
+ static int addrconf_ifdown(struct net_device *dev, int how);
+-static void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags);
++void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags);
+ static void addrconf_dad_timer(unsigned long data);
+-static void addrconf_dad_completed(struct inet6_ifaddr *ifp);
++void addrconf_dad_completed(struct inet6_ifaddr *ifp);
+ static void addrconf_rs_timer(unsigned long data);
+ static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifa);
+@@ -330,38 +332,6 @@ static struct inet6_dev * ipv6_find_idev
+       return idev;
+ }
+-void ipv6_addr_prefix(struct in6_addr *prefix,
+-      struct in6_addr *addr, int prefix_len)
+-{
+-      unsigned long mask;
+-      int ncopy, nbits;
+-
+-      memset(prefix, 0, sizeof(*prefix));
+-
+-      if (prefix_len <= 0)
+-              return;
+-      if (prefix_len > 128)
+-              prefix_len = 128;
+-
+-      ncopy = prefix_len / 32;
+-      switch (ncopy) {
+-      case 4: prefix->s6_addr32[3] = addr->s6_addr32[3];
+-      case 3: prefix->s6_addr32[2] = addr->s6_addr32[2];
+-      case 2: prefix->s6_addr32[1] = addr->s6_addr32[1];
+-      case 1: prefix->s6_addr32[0] = addr->s6_addr32[0];
+-      case 0: break;
+-      }
+-      nbits = prefix_len % 32;
+-      if (nbits == 0)
+-              return;
+-
+-      mask = ~((1 << (32 - nbits)) - 1);
+-      mask = htonl(mask);
+-
+-      prefix->s6_addr32[ncopy] = addr->s6_addr32[ncopy] & mask;
+-}
+-
+-
+ static void dev_forward_change(struct inet6_dev *idev)
+ {
+       struct net_device *dev;
+@@ -513,7 +483,7 @@ ipv6_add_addr(struct inet6_dev *idev, co
+ /* This function wants to get referenced ifp and releases it before return */
+-static void ipv6_del_addr(struct inet6_ifaddr *ifp)
++void ipv6_del_addr(struct inet6_ifaddr *ifp)
+ {
+       struct inet6_ifaddr *ifa, **ifap;
+       struct inet6_dev *idev = ifp->idev;
+@@ -662,6 +632,12 @@ out:
+       if (match)
+               in6_ifa_put(match);
++      /* The home address is always used as source address in
++       * MIPL mobile IPv6
++       */
++      if (scope != IFA_HOST && scope != IFA_LINK)
++              addrconf_get_mipv6_home_address(saddr);
++
+       return err;
+ }
+@@ -815,7 +791,7 @@ void addrconf_leave_solict(struct net_de
+ }
+-static int ipv6_generate_eui64(u8 *eui, struct net_device *dev)
++int ipv6_generate_eui64(u8 *eui, struct net_device *dev)
+ {
+       switch (dev->type) {
+       case ARPHRD_ETHER:
+@@ -840,7 +816,7 @@ static int ipv6_generate_eui64(u8 *eui, 
+       return -1;
+ }
+-static int ipv6_inherit_eui64(u8 *eui, struct inet6_dev *idev)
++int ipv6_inherit_eui64(u8 *eui, struct inet6_dev *idev)
+ {
+       int err = -1;
+       struct inet6_ifaddr *ifp;
+@@ -1407,6 +1383,24 @@ static void addrconf_sit_config(struct n
+               sit_route_add(dev);
+ }
++/**
++ * addrconf_ipv6_tunnel_config - configure IPv6 tunnel device
++ * @dev: tunnel device
++ **/
++
++static void addrconf_ipv6_tunnel_config(struct net_device *dev)
++{
++      struct inet6_dev *idev;
++
++      ASSERT_RTNL();
++
++      /* Assign inet6_dev structure to tunnel device */
++      if ((idev = ipv6_find_idev(dev)) == NULL) {
++              printk(KERN_DEBUG "init ipv6 tunnel: add_dev failed\n");
++              return;
++      }
++}
++
+ int addrconf_notify(struct notifier_block *this, unsigned long event, 
+                   void * data)
+@@ -1421,6 +1415,10 @@ int addrconf_notify(struct notifier_bloc
+                       addrconf_sit_config(dev);
+                       break;
++              case ARPHRD_TUNNEL6:
++                      addrconf_ipv6_tunnel_config(dev);
++                      break;
++
+               case ARPHRD_LOOPBACK:
+                       init_loopback(dev);
+                       break;
+@@ -1602,7 +1600,7 @@ out:
+ /*
+  *    Duplicate Address Detection
+  */
+-static void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags)
++void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags)
+ {
+       struct net_device *dev;
+       unsigned long rand_num;
+@@ -1667,7 +1665,7 @@ static void addrconf_dad_timer(unsigned 
+       in6_ifa_put(ifp);
+ }
+-static void addrconf_dad_completed(struct inet6_ifaddr *ifp)
++void addrconf_dad_completed(struct inet6_ifaddr *ifp)
+ {
+       struct net_device *     dev = ifp->idev->dev;
+@@ -1676,7 +1674,7 @@ static void addrconf_dad_completed(struc
+        */
+       ipv6_ifa_notify(RTM_NEWADDR, ifp);
+-
++      notifier_call_chain(&inet6addr_chain,NETDEV_UP,ifp);
+       /* If added prefix is link local and forwarding is off,
+          start sending router solicitations.
+        */
+@@ -1877,8 +1875,20 @@ inet6_rtm_newaddr(struct sk_buff *skb, s
+       if (rta[IFA_LOCAL-1]) {
+               if (pfx && memcmp(pfx, RTA_DATA(rta[IFA_LOCAL-1]), sizeof(*pfx)))
+                       return -EINVAL;
++              if (ifm->ifa_flags & IFA_F_HOMEADDR && !rta[IFA_HOMEAGENT-1])
++                      return -EINVAL;
+               pfx = RTA_DATA(rta[IFA_LOCAL-1]);
+       }
++      if (rta[IFA_HOMEAGENT-1]) {
++              struct in6_addr *ha;
++              if (pfx == NULL || !(ifm->ifa_flags & IFA_F_HOMEADDR))
++                      return -EINVAL;
++              if (RTA_PAYLOAD(rta[IFA_HOMEAGENT-1]) < sizeof(*ha))
++                      return -EINVAL;
++              ha = RTA_DATA(rta[IFA_HOMEAGENT-1]);
++              addrconf_set_mipv6_mn_home(ifm->ifa_index, pfx, ifm->ifa_prefixlen,
++                                         ha, ifm->ifa_prefixlen);
++      }
+       if (pfx == NULL)
+               return -EINVAL;
+diff -uprN linux-2.4.25.old/net/ipv6/af_inet6.c linux-2.4.25/net/ipv6/af_inet6.c
+--- linux-2.4.25.old/net/ipv6/af_inet6.c       2003-11-28 18:26:21.000000000 +0000
++++ linux-2.4.25/net/ipv6/af_inet6.c   2004-06-26 11:29:30.000000000 +0100
+@@ -58,6 +58,9 @@
+ #include <net/transp_v6.h>
+ #include <net/ip6_route.h>
+ #include <net/addrconf.h>
++#ifdef CONFIG_IPV6_TUNNEL
++#include <net/ipv6_tunnel.h>
++#endif
+ #include <asm/uaccess.h>
+ #include <asm/system.h>
+@@ -646,6 +649,11 @@ static int __init inet6_init(void)
+       err = ndisc_init(&inet6_family_ops);
+       if (err)
+               goto ndisc_fail;
++#ifdef CONFIG_IPV6_TUNNEL
++      err = ip6_tunnel_init();
++      if (err)
++              goto ip6_tunnel_fail;
++#endif
+       err = igmp6_init(&inet6_family_ops);
+       if (err)
+               goto igmp_fail;
+@@ -698,6 +706,10 @@ proc_raw6_fail:
+ #endif
+ igmp_fail:
+       ndisc_cleanup();
++#ifdef CONFIG_IPV6_TUNNEL
++      ip6_tunnel_cleanup();
++ip6_tunnel_fail:
++#endif
+ ndisc_fail:
+       icmpv6_cleanup();
+ icmp_fail:
+@@ -730,6 +742,9 @@ static void inet6_exit(void)
+       ip6_route_cleanup();
+       ipv6_packet_cleanup();
+       igmp6_cleanup();
++#ifdef CONFIG_IPV6_TUNNEL
++      ip6_tunnel_cleanup();
++#endif
+       ndisc_cleanup();
+       icmpv6_cleanup();
+ #ifdef CONFIG_SYSCTL
+diff -uprN linux-2.4.25.old/net/ipv6/exthdrs.c linux-2.4.25/net/ipv6/exthdrs.c
+--- linux-2.4.25.old/net/ipv6/exthdrs.c        2003-08-25 12:44:44.000000000 +0100
++++ linux-2.4.25/net/ipv6/exthdrs.c    2004-06-26 11:29:30.000000000 +0100
+@@ -41,6 +41,9 @@
+ #include <net/ip6_route.h>
+ #include <net/addrconf.h>
++#include <net/mipglue.h>
++#include <net/mipv6.h>
++
+ #include <asm/uaccess.h>
+ /*
+@@ -160,7 +163,8 @@ bad:
+  *****************************/
+ struct tlvtype_proc tlvprocdestopt_lst[] = {
+-      /* No destination options are defined now */
++      /* Mobility Support destination options */
++      {MIPV6_TLV_HOMEADDR,    mipv6_handle_dstopt},
+       {-1,                    NULL}
+ };
+@@ -210,6 +214,7 @@ static int ipv6_routing_header(struct sk
+       struct ipv6_rt_hdr *hdr;
+       struct rt0_hdr *rthdr;
++      struct rt2_hdr *rt2hdr;
+       if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8) ||
+           !pskb_may_pull(skb, (skb->h.raw-skb->data)+((skb->h.raw[1]+1)<<3))) {
+@@ -225,17 +230,25 @@ static int ipv6_routing_header(struct sk
+               kfree_skb(skb);
+               return -1;
+       }
+-
++      /* Silently discard invalid packets containing RTH type 2 */ 
++      if (hdr->type == IPV6_SRCRT_TYPE_2 && 
++          (hdr->hdrlen != 2 || hdr->segments_left != 1)) {
++              kfree_skb(skb);
++              return -1;
++      }
+ looped_back:
+       if (hdr->segments_left == 0) {
+-              opt->srcrt = skb->h.raw - skb->nh.raw;
++              if (hdr->type == IPV6_SRCRT_TYPE_0)
++                      opt->srcrt = skb->h.raw - skb->nh.raw;
++              else if (hdr->type == IPV6_SRCRT_TYPE_2)
++                      opt->srcrt2 = skb->h.raw - skb->nh.raw;
+               skb->h.raw += (hdr->hdrlen + 1) << 3;
+               opt->dst0 = opt->dst1;
+               opt->dst1 = 0;
+               return (&hdr->nexthdr) - skb->nh.raw;
+       }
+-      if (hdr->type != IPV6_SRCRT_TYPE_0) {
++      if (hdr->type != IPV6_SRCRT_TYPE_0 && hdr->type != IPV6_SRCRT_TYPE_2) {
+               icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->type) - skb->nh.raw);
+               return -1;
+       }
+@@ -275,9 +288,20 @@ looped_back:
+       i = n - --hdr->segments_left;
+-      rthdr = (struct rt0_hdr *) hdr;
+-      addr = rthdr->addr;
+-      addr += i - 1;
++      if (hdr->type == IPV6_SRCRT_TYPE_0) {
++              rthdr = (struct rt0_hdr *) hdr;
++              addr = rthdr->addr;
++              addr += i - 1;
++      } else {
++              /* check that address is this node's home address */
++              rt2hdr = (struct rt2_hdr *) hdr;
++              addr = &rt2hdr->addr;
++              if (!ipv6_chk_addr(addr, NULL) || 
++                  !ipv6_chk_mip_home_addr(addr)) {
++                      kfree_skb(skb);
++                      return -1;
++              }
++      }
+       addr_type = ipv6_addr_type(addr);
+@@ -330,6 +354,10 @@ looped_back:
+    temporary (or permanent) backdoor.
+    If listening socket set IPV6_RTHDR to 2, then we invert header.
+                                                    --ANK (980729)
++
++   By the Mobile IPv6 specification Type 2 routing header MUST NOT be
++   inverted.
++                                                   --AJT (20020917)
+  */
+ struct ipv6_txoptions *
+@@ -352,6 +380,18 @@ ipv6_invert_rthdr(struct sock *sk, struc
+       struct ipv6_txoptions *opt;
+       int hdrlen = ipv6_optlen(hdr);
++      if (hdr->type == IPV6_SRCRT_TYPE_2) {
++              opt = sock_kmalloc(sk, sizeof(*opt) + hdrlen, GFP_ATOMIC);
++              if (opt == NULL)
++                      return NULL;
++              memset(opt, 0, sizeof(*opt));
++              opt->tot_len = sizeof(*opt) + hdrlen;
++              opt->srcrt = (void*)(opt+1);
++              opt->opt_nflen = hdrlen;
++              memcpy(opt->srcrt, hdr, sizeof(struct rt2_hdr));
++              return opt;
++      }
++
+       if (hdr->segments_left ||
+           hdr->type != IPV6_SRCRT_TYPE_0 ||
+           hdr->hdrlen & 0x01)
+@@ -622,8 +662,18 @@ u8 *ipv6_build_nfrag_opts(struct sk_buff
+       if (opt) {
+               if (opt->dst0opt)
+                       prev_hdr = ipv6_build_exthdr(skb, prev_hdr, NEXTHDR_DEST, opt->dst0opt);
+-              if (opt->srcrt)
+-                      prev_hdr = ipv6_build_rthdr(skb, prev_hdr, opt->srcrt, daddr);
++              if (opt->srcrt) {
++                      if (opt->srcrt2) {
++                              struct in6_addr *rt2_hop = &((struct rt2_hdr *)opt->srcrt2)->addr;
++                              prev_hdr = ipv6_build_rthdr(skb, prev_hdr, opt->srcrt, rt2_hop);
++                      } else
++                              prev_hdr = ipv6_build_rthdr(skb, prev_hdr, opt->srcrt, daddr);
++              }
++              if (opt->srcrt2) {
++                      struct inet6_skb_parm *parm = (struct inet6_skb_parm *)skb->cb;
++                      ipv6_addr_copy(&parm->hoa, daddr);
++                      prev_hdr = ipv6_build_rthdr(skb, prev_hdr, opt->srcrt2, daddr);
++              }
+       }
+       return prev_hdr;
+ }
+@@ -684,6 +734,11 @@ void ipv6_push_nfrag_opts(struct sk_buff
+                         u8 *proto,
+                         struct in6_addr **daddr)
+ {
++      if (opt->srcrt2) {
++              struct inet6_skb_parm *parm = (struct inet6_skb_parm *)skb->cb;
++              ipv6_addr_copy(&parm->hoa, *daddr);
++              ipv6_push_rthdr(skb, proto, opt->srcrt2, daddr);
++      }
+       if (opt->srcrt)
+               ipv6_push_rthdr(skb, proto, opt->srcrt, daddr);
+       if (opt->dst0opt)
+@@ -719,6 +774,8 @@ ipv6_dup_options(struct sock *sk, struct
+                       *((char**)&opt2->auth) += dif;
+               if (opt2->srcrt)
+                       *((char**)&opt2->srcrt) += dif;
++              if (opt2->srcrt2)
++                      *((char**)&opt2->srcrt2) += dif;
+       }
+       return opt2;
+ }
+diff -uprN linux-2.4.25.old/net/ipv6/icmp.c linux-2.4.25/net/ipv6/icmp.c
+--- linux-2.4.25.old/net/ipv6/icmp.c   2003-11-28 18:26:21.000000000 +0000
++++ linux-2.4.25/net/ipv6/icmp.c       2004-06-26 11:29:30.000000000 +0100
+@@ -61,6 +61,8 @@
+ #include <net/addrconf.h>
+ #include <net/icmp.h>
++#include <net/mipglue.h>
++
+ #include <asm/uaccess.h>
+ #include <asm/system.h>
+@@ -364,6 +366,8 @@ void icmpv6_send(struct sk_buff *skb, in
+       msg.len = len;
++      icmpv6_swap_mipv6_addrs(skb);
++
+       ip6_build_xmit(sk, icmpv6_getfrag, &msg, &fl, len, NULL, -1,
+                      MSG_DONTWAIT);
+       if (type >= ICMPV6_DEST_UNREACH && type <= ICMPV6_PARAMPROB)
+@@ -562,13 +566,13 @@ int icmpv6_rcv(struct sk_buff *skb)
+               rt6_pmtu_discovery(&orig_hdr->daddr, &orig_hdr->saddr, dev,
+                                  ntohl(hdr->icmp6_mtu));
+-              /*
+-               *      Drop through to notify
+-               */
++              icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu);
++              break;
+       case ICMPV6_DEST_UNREACH:
+-      case ICMPV6_TIME_EXCEED:
+       case ICMPV6_PARAMPROB:
++              mipv6_icmp_rcv(skb);
++      case ICMPV6_TIME_EXCEED:
+               icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu);
+               break;
+@@ -597,6 +601,13 @@ int icmpv6_rcv(struct sk_buff *skb)
+       case ICMPV6_MGM_REDUCTION:
+               break;
++      case MIPV6_DHAAD_REQUEST:
++      case MIPV6_DHAAD_REPLY:
++      case MIPV6_PREFIX_SOLICIT:
++      case MIPV6_PREFIX_ADV:
++              mipv6_icmp_rcv(skb);
++              break;
++
+       default:
+               if (net_ratelimit())
+                       printk(KERN_DEBUG "icmpv6: msg of unkown type\n");
+diff -uprN linux-2.4.25.old/net/ipv6/ip6_fib.c linux-2.4.25/net/ipv6/ip6_fib.c
+--- linux-2.4.25.old/net/ipv6/ip6_fib.c        2003-08-25 12:44:44.000000000 +0100
++++ linux-2.4.25/net/ipv6/ip6_fib.c    2004-06-26 11:29:30.000000000 +0100
+@@ -18,6 +18,7 @@
+  *    Yuji SEKIYA @USAGI:     Support default route on router node;
+  *                            remove ip6_null_entry from the top of
+  *                            routing table.
++ *    Ville Nuorvala:         Fixes to source address based routing
+  */
+ #include <linux/config.h>
+ #include <linux/errno.h>
+@@ -40,7 +41,6 @@
+ #include <net/ip6_route.h>
+ #define RT6_DEBUG 2
+-#undef CONFIG_IPV6_SUBTREES
+ #if RT6_DEBUG >= 3
+ #define RT6_TRACE(x...) printk(KERN_DEBUG x)
+@@ -500,6 +500,8 @@ static __inline__ void fib6_start_gc(str
+               mod_timer(&ip6_fib_timer, jiffies + ip6_rt_gc_interval);
+ }
++static struct rt6_info * fib6_find_prefix(struct fib6_node *fn);
++
+ /*
+  *    Add routing information to the routing tree.
+  *    <destination addr>/<source addr>
+@@ -508,17 +510,19 @@ static __inline__ void fib6_start_gc(str
+ int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nlmsghdr *nlh)
+ {
+-      struct fib6_node *fn;
++      struct fib6_node *fn = root;
+       int err = -ENOMEM;
+-      fn = fib6_add_1(root, &rt->rt6i_dst.addr, sizeof(struct in6_addr),
+-                      rt->rt6i_dst.plen, (u8*) &rt->rt6i_dst - (u8*) rt);
++#ifdef CONFIG_IPV6_SUBTREES
++      struct fib6_node *pn = NULL;
++      fn = fib6_add_1(root, &rt->rt6i_src.addr, sizeof(struct in6_addr),
++                      rt->rt6i_src.plen, (u8*) &rt->rt6i_src - (u8*) rt);
++      
+       if (fn == NULL)
+               goto out;
+-#ifdef CONFIG_IPV6_SUBTREES
+-      if (rt->rt6i_src.plen) {
++      if (rt->rt6i_dst.plen) {
+               struct fib6_node *sn;
+               if (fn->subtree == NULL) {
+@@ -546,9 +550,9 @@ int fib6_add(struct fib6_node *root, str
+                       /* Now add the first leaf node to new subtree */
+-                      sn = fib6_add_1(sfn, &rt->rt6i_src.addr,
+-                                      sizeof(struct in6_addr), rt->rt6i_src.plen,
+-                                      (u8*) &rt->rt6i_src - (u8*) rt);
++                      sn = fib6_add_1(sfn, &rt->rt6i_dst.addr,
++                                      sizeof(struct in6_addr), rt->rt6i_dst.plen,
++                                      (u8*) &rt->rt6i_dst - (u8*) rt);
+                       if (sn == NULL) {
+                               /* If it is failed, discard just allocated
+@@ -562,21 +566,30 @@ int fib6_add(struct fib6_node *root, str
+                       /* Now link new subtree to main tree */
+                       sfn->parent = fn;
+                       fn->subtree = sfn;
+-                      if (fn->leaf == NULL) {
+-                              fn->leaf = rt;
+-                              atomic_inc(&rt->rt6i_ref);
+-                      }
+               } else {
+-                      sn = fib6_add_1(fn->subtree, &rt->rt6i_src.addr,
+-                                      sizeof(struct in6_addr), rt->rt6i_src.plen,
+-                                      (u8*) &rt->rt6i_src - (u8*) rt);
++                      sn = fib6_add_1(fn->subtree, &rt->rt6i_dst.addr,
++                                      sizeof(struct in6_addr), rt->rt6i_dst.plen,
++                                      (u8*) &rt->rt6i_dst - (u8*) rt);
+                       if (sn == NULL)
+                               goto st_failure;
+               }
++              /* fib6_add_1 might have cleared the old leaf pointer */
++              if (fn->leaf == NULL) {
++                      fn->leaf = rt;
++                      atomic_inc(&rt->rt6i_ref);
++              }
++
++              pn = fn;
+               fn = sn;
+       }
++#else 
++      fn = fib6_add_1(root, &rt->rt6i_dst.addr, sizeof(struct in6_addr),
++                      rt->rt6i_dst.plen, (u8*) &rt->rt6i_dst - (u8*) rt);
++
++      if (fn == NULL)
++              goto out;
+ #endif
+       err = fib6_add_rt2node(fn, rt, nlh);
+@@ -588,8 +601,30 @@ int fib6_add(struct fib6_node *root, str
+       }
+ out:
+-      if (err)
++      if (err) {
++#ifdef CONFIG_IPV6_SUBTREES
++
++              /* If fib6_add_1 has cleared the old leaf pointer in the 
++                 super-tree leaf node, we have to find a new one for it. 
++                 
++                 This situation will never arise in the sub-tree since 
++                 the node will at least have the route that caused 
++                 fib6_add_rt2node to fail.
++              */
++
++              if (pn && !(pn->fn_flags & RTN_RTINFO)) {
++                      pn->leaf = fib6_find_prefix(pn);
++#if RT6_DEBUG >= 2
++                      if (!pn->leaf) {
++                              BUG_TRAP(pn->leaf);
++                              pn->leaf = &ip6_null_entry;
++                      }
++#endif
++                      atomic_inc(&pn->leaf->rt6i_ref);
++              }
++#endif
+               dst_free(&rt->u.dst);
++      }
+       return err;
+ #ifdef CONFIG_IPV6_SUBTREES
+@@ -597,8 +632,8 @@ out:
+          is orphan. If it is, shoot it.
+        */
+ st_failure:
+-      if (fn && !(fn->fn_flags&RTN_RTINFO|RTN_ROOT))
+-              fib_repair_tree(fn);
++      if (fn && !(fn->fn_flags & (RTN_RTINFO | RTN_ROOT)))
++              fib6_repair_tree(fn);
+       dst_free(&rt->u.dst);
+       return err;
+ #endif
+@@ -641,22 +676,28 @@ static struct fib6_node * fib6_lookup_1(
+               break;
+       }
+-      while ((fn->fn_flags & RTN_ROOT) == 0) {
++      for (;;) {
+ #ifdef CONFIG_IPV6_SUBTREES
+               if (fn->subtree) {
+-                      struct fib6_node *st;
+-                      struct lookup_args *narg;
+-
+-                      narg = args + 1;
+-
+-                      if (narg->addr) {
+-                              st = fib6_lookup_1(fn->subtree, narg);
++                      struct rt6key *key;
+-                              if (st && !(st->fn_flags & RTN_ROOT))
+-                                      return st;
++                      key = (struct rt6key *) ((u8 *) fn->leaf +
++                                               args->offset);
++                      
++                      if (addr_match(&key->addr, args->addr, key->plen)) {
++                              struct fib6_node *st;
++                              struct lookup_args *narg = args + 1;
++                              if (!ipv6_addr_any(narg->addr)) {
++                                      st = fib6_lookup_1(fn->subtree, narg);
++                                      
++                                      if (st && !(st->fn_flags & RTN_ROOT))
++                                              return st;
++                              } 
+                       }
+               }
+ #endif
++              if (fn->fn_flags & RTN_ROOT)
++                      break;
+               if (fn->fn_flags & RTN_RTINFO) {
+                       struct rt6key *key;
+@@ -680,13 +721,22 @@ struct fib6_node * fib6_lookup(struct fi
+       struct lookup_args args[2];
+       struct rt6_info *rt = NULL;
+       struct fib6_node *fn;
++#ifdef CONFIG_IPV6_SUBTREES
++      struct in6_addr saddr_buf;
++#endif
++#ifdef CONFIG_IPV6_SUBTREES
++      if (saddr == NULL) {
++              memset(&saddr_buf, 0, sizeof(struct in6_addr));
++              saddr = &saddr_buf;
++      }
++      args[0].offset = (u8*) &rt->rt6i_src - (u8*) rt;
++      args[0].addr = saddr;
++      args[1].offset = (u8*) &rt->rt6i_dst - (u8*) rt;
++      args[1].addr = daddr;
++#else 
+       args[0].offset = (u8*) &rt->rt6i_dst - (u8*) rt;
+       args[0].addr = daddr;
+-
+-#ifdef CONFIG_IPV6_SUBTREES
+-      args[1].offset = (u8*) &rt->rt6i_src - (u8*) rt;
+-      args[1].addr = saddr;
+ #endif
+       fn = fib6_lookup_1(root, args);
+@@ -739,19 +789,25 @@ struct fib6_node * fib6_locate(struct fi
+ {
+       struct rt6_info *rt = NULL;
+       struct fib6_node *fn;
+-
+-      fn = fib6_locate_1(root, daddr, dst_len,
+-                         (u8*) &rt->rt6i_dst - (u8*) rt);
+-
+ #ifdef CONFIG_IPV6_SUBTREES
+-      if (src_len) {
+-              BUG_TRAP(saddr!=NULL);
+-              if (fn == NULL)
+-                      fn = fn->subtree;
++      struct in6_addr saddr_buf;
++
++      if (saddr == NULL) {
++              memset(&saddr_buf, 0, sizeof(struct in6_addr));
++              saddr = &saddr_buf;
++      }
++      fn = fib6_locate_1(root, saddr, src_len, 
++                         (u8*) &rt->rt6i_src - (u8*) rt);
++      if (dst_len) {
+               if (fn)
+-                      fn = fib6_locate_1(fn, saddr, src_len,
+-                                         (u8*) &rt->rt6i_src - (u8*) rt);
++                      fn = fib6_locate_1(fn->subtree, daddr, dst_len,
++                                         (u8*) &rt->rt6i_dst - (u8*) rt);
++              else 
++                      return NULL;
+       }
++#else
++      fn = fib6_locate_1(root, daddr, dst_len,
++                         (u8*) &rt->rt6i_dst - (u8*) rt);
+ #endif
+       if (fn && fn->fn_flags&RTN_RTINFO)
+@@ -939,7 +995,7 @@ static void fib6_del_route(struct fib6_n
+                       }
+                       fn = fn->parent;
+               }
+-              /* No more references are possiible at this point. */
++              /* No more references are possible at this point. */
+               if (atomic_read(&rt->rt6i_ref) != 1) BUG();
+       }
+diff -uprN linux-2.4.25.old/net/ipv6/ip6_input.c linux-2.4.25/net/ipv6/ip6_input.c
+--- linux-2.4.25.old/net/ipv6/ip6_input.c      2003-08-25 12:44:44.000000000 +0100
++++ linux-2.4.25/net/ipv6/ip6_input.c  2004-06-26 11:29:30.000000000 +0100
+@@ -40,13 +40,42 @@
+ #include <net/ip6_route.h>
+ #include <net/addrconf.h>
++static inline int ip6_proxy_chk(struct sk_buff *skb)
++{
++      struct ipv6hdr *hdr = skb->nh.ipv6h;
+-
++      if (ipv6_addr_type(&hdr->daddr)&IPV6_ADDR_UNICAST &&
++          pneigh_lookup(&nd_tbl, &hdr->daddr, skb->dev, 0)) {
++              u8 nexthdr = hdr->nexthdr;
++              int offset;
++              struct icmp6hdr msg;
++
++              if (ipv6_ext_hdr(nexthdr)) {
++                      offset = ipv6_skip_exthdr(skb, sizeof(*hdr), &nexthdr, 
++                                                skb->len - sizeof(*hdr));
++                      if (offset < 0)
++                              return 0;
++              } else
++                      offset = sizeof(*hdr);
++
++              /* capture unicast NUD probes on behalf of the proxied node */
++
++              if (nexthdr == IPPROTO_ICMPV6 &&
++                  !skb_copy_bits(skb, offset, &msg, sizeof(msg)) &&
++                  msg.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION) {
++                      return 1;
++              }
++      }
++      return 0;
++}
++ 
+ static inline int ip6_rcv_finish( struct sk_buff *skb) 
+ {
+-      if (skb->dst == NULL)
+-              ip6_route_input(skb);
+-
++      if (skb->dst == NULL) {
++              if (ip6_proxy_chk(skb)) 
++                      return ip6_input(skb);
++              ip6_route_input(skb);
++      }
+       return skb->dst->input(skb);
+ }
+diff -uprN linux-2.4.25.old/net/ipv6/ip6_output.c linux-2.4.25/net/ipv6/ip6_output.c
+--- linux-2.4.25.old/net/ipv6/ip6_output.c     2003-08-25 12:44:44.000000000 +0100
++++ linux-2.4.25/net/ipv6/ip6_output.c 2004-06-26 11:29:30.000000000 +0100
+@@ -50,6 +50,8 @@
+ #include <net/rawv6.h>
+ #include <net/icmp.h>
++#include <net/mipglue.h>
++
+ static __inline__ void ipv6_select_ident(struct sk_buff *skb, struct frag_hdr *fhdr)
+ {
+       static u32 ipv6_fragmentation_id = 1;
+@@ -194,7 +196,14 @@ int ip6_xmit(struct sock *sk, struct sk_
+       u8  proto = fl->proto;
+       int seg_len = skb->len;
+       int hlimit;
++      int retval;
++      struct ipv6_txoptions *orig_opt = opt;
++
++      opt = ip6_add_mipv6_txoptions(sk, skb, orig_opt, fl, &dst);
++      if(orig_opt && !opt)
++              return -ENOMEM;
++              
+       if (opt) {
+               int head_room;
+@@ -209,8 +218,11 @@ int ip6_xmit(struct sock *sk, struct sk_
+                       struct sk_buff *skb2 = skb_realloc_headroom(skb, head_room);
+                       kfree_skb(skb);
+                       skb = skb2;
+-                      if (skb == NULL)
++                      if (skb == NULL) {
++                              ip6_free_mipv6_txoptions(opt, orig_opt);
++
+                               return -ENOBUFS;
++                      }
+                       if (sk)
+                               skb_set_owner_w(skb, sk);
+               }
+@@ -242,7 +254,10 @@ int ip6_xmit(struct sock *sk, struct sk_
+       if (skb->len <= dst->pmtu) {
+               IP6_INC_STATS(Ip6OutRequests);
+-              return NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute);
++              ip6_mark_mipv6_packet(opt, skb);
++              retval = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute);
++              ip6_free_mipv6_txoptions(opt, orig_opt);
++              return retval; 
+       }
+       if (net_ratelimit())
+@@ -250,6 +265,9 @@ int ip6_xmit(struct sock *sk, struct sk_
+       skb->dev = dst->dev;
+       icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, dst->pmtu, skb->dev);
+       kfree_skb(skb);
++
++      ip6_free_mipv6_txoptions(opt, orig_opt);
++
+       return -EMSGSIZE;
+ }
+@@ -473,6 +491,7 @@ static int ip6_frag_xmit(struct sock *sk
+                       IP6_INC_STATS(Ip6FragCreates);
+                       IP6_INC_STATS(Ip6OutRequests);
++                      ip6_mark_mipv6_packet(opt, skb);
+                       err = NF_HOOK(PF_INET6,NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute);
+                       if (err) {
+                               kfree_skb(last_skb);
+@@ -499,6 +518,7 @@ static int ip6_frag_xmit(struct sock *sk
+       IP6_INC_STATS(Ip6FragCreates);
+       IP6_INC_STATS(Ip6FragOKs);
+       IP6_INC_STATS(Ip6OutRequests);
++      ip6_mark_mipv6_packet(opt, last_skb);
+       return NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, last_skb, NULL,dst->dev, ip6_maybe_reroute);
+ }
+@@ -509,26 +529,43 @@ int ip6_build_xmit(struct sock *sk, inet
+       struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+       struct in6_addr *final_dst = NULL;
+       struct dst_entry *dst;
++      struct rt6_info *rt;
+       int err = 0;
+       unsigned int pktlength, jumbolen, mtu;
+       struct in6_addr saddr;
++      struct ipv6_txoptions *orig_opt = opt; 
++#ifdef CONFIG_IPV6_SUBTREES
++      struct dst_entry *org_dst;
++#endif
++
++      opt = ip6_add_mipv6_txoptions(sk, NULL, orig_opt, fl, NULL);
++
++      if(orig_opt && !opt)
++              return -ENOMEM;
+       if (opt && opt->srcrt) {
+               struct rt0_hdr *rt0 = (struct rt0_hdr *) opt->srcrt;
+               final_dst = fl->fl6_dst;
+               fl->fl6_dst = rt0->addr;
+-      }
++      } else if (opt && opt->srcrt2) {
++                struct rt2_hdr *rt2 = (struct rt2_hdr *) opt->srcrt2;
++                final_dst = fl->fl6_dst;
++                fl->fl6_dst = &rt2->addr;
++        }
+       if (!fl->oif && ipv6_addr_is_multicast(fl->nl_u.ip6_u.daddr))
+               fl->oif = np->mcast_oif;
+       dst = __sk_dst_check(sk, np->dst_cookie);
++#ifdef CONFIG_IPV6_SUBTREES
++      org_dst = dst;
++#endif
+       if (dst) {
+-              struct rt6_info *rt = (struct rt6_info*)dst;
++              rt = (struct rt6_info*)dst;
+                       /* Yes, checking route validity in not connected
+                          case is not very simple. Take into account,
+-                         that we do not support routing by source, TOS,
++                         that we do not support routing by TOS,
+                          and MSG_DONTROUTE            --ANK (980726)
+                          1. If route was host route, check that
+@@ -548,6 +585,13 @@ int ip6_build_xmit(struct sock *sk, inet
+                     ipv6_addr_cmp(fl->fl6_dst, &rt->rt6i_dst.addr))
+                    && (np->daddr_cache == NULL ||
+                        ipv6_addr_cmp(fl->fl6_dst, np->daddr_cache)))
++#ifdef CONFIG_IPV6_SUBTREES
++                  || (fl->fl6_src != NULL
++                      && (rt->rt6i_src.plen != 128 ||
++                          ipv6_addr_cmp(fl->fl6_src, &rt->rt6i_src.addr))
++                      && (np->saddr_cache == NULL ||
++                          ipv6_addr_cmp(fl->fl6_src, np->saddr_cache)))
++#endif
+                   || (fl->oif && fl->oif != dst->dev->ifindex)) {
+                       dst = NULL;
+               } else
+@@ -560,21 +604,42 @@ int ip6_build_xmit(struct sock *sk, inet
+       if (dst->error) {
+               IP6_INC_STATS(Ip6OutNoRoutes);
+               dst_release(dst);
++              ip6_free_mipv6_txoptions(opt, orig_opt);
+               return -ENETUNREACH;
+       }
+       if (fl->fl6_src == NULL) {
+               err = ipv6_get_saddr(dst, fl->fl6_dst, &saddr);
+-
+               if (err) {
+ #if IP6_DEBUG >= 2
+                       printk(KERN_DEBUG "ip6_build_xmit: "
+                              "no available source address\n");
+ #endif
++
++#ifdef CONFIG_IPV6_SUBTREES
++                      if (dst != org_dst) {
++                              dst_release(dst);
++                              dst = org_dst;
++                      }
++#endif                
+                       goto out;
+               }
+               fl->fl6_src = &saddr;
+       }
++#ifdef CONFIG_IPV6_SUBTREES
++      rt = (struct rt6_info*)dst;
++      if (dst != org_dst || rt->rt6i_src.plen != 128 ||
++          ipv6_addr_cmp(fl->fl6_src, &rt->rt6i_src.addr)) {
++              dst_release(dst);
++              dst = ip6_route_output(sk, fl);
++              if (dst->error) {
++                      IP6_INC_STATS(Ip6OutNoRoutes);
++                      dst_release(dst);
++                      ip6_free_mipv6_txoptions(opt, orig_opt);
++                      return -ENETUNREACH;
++              }
++      }
++#endif
+       pktlength = length;
+       if (hlimit < 0) {
+@@ -667,6 +732,7 @@ int ip6_build_xmit(struct sock *sk, inet
+               if (!err) {
+                       IP6_INC_STATS(Ip6OutRequests);
++                      ip6_mark_mipv6_packet(opt, skb);
+                       err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute);
+               } else {
+                       err = -EFAULT;
+@@ -688,9 +754,14 @@ int ip6_build_xmit(struct sock *sk, inet
+        *      cleanup
+        */
+ out:
+-      ip6_dst_store(sk, dst, fl->nl_u.ip6_u.daddr == &np->daddr ? &np->daddr : NULL);
++      ip6_dst_store(sk, dst, 
++                    fl->nl_u.ip6_u.daddr == &np->daddr ? &np->daddr : NULL,
++                    fl->nl_u.ip6_u.saddr == &np->saddr ? &np->saddr : NULL);
+       if (err > 0)
+               err = np->recverr ? net_xmit_errno(err) : 0;
++
++      ip6_free_mipv6_txoptions(opt, orig_opt);
++
+       return err;
+ }
+@@ -769,6 +840,15 @@ int ip6_forward(struct sk_buff *skb)
+               return -ETIMEDOUT;
+       }
++      /* The proxying router can't forward traffic sent to a link-local
++         address, so signal the sender and discard the packet. This
++         behavior is required by the MIPv6 specification. */
++
++      if (ipv6_addr_type(&hdr->daddr) & IPV6_ADDR_LINKLOCAL && 
++          skb->dev && pneigh_lookup(&nd_tbl, &hdr->daddr, skb->dev, 0)) {
++              dst_link_failure(skb);
++              goto drop;
++      }
+       /* IPv6 specs say nothing about it, but it is clear that we cannot
+          send redirects to source routed frames.
+        */
+diff -uprN linux-2.4.25.old/net/ipv6/ipv6_syms.c linux-2.4.25/net/ipv6/ipv6_syms.c
+--- linux-2.4.25.old/net/ipv6/ipv6_syms.c      2003-11-28 18:26:21.000000000 +0000
++++ linux-2.4.25/net/ipv6/ipv6_syms.c  2004-06-26 11:29:30.000000000 +0100
+@@ -6,6 +6,8 @@
+ #include <net/ipv6.h>
+ #include <net/addrconf.h>
+ #include <net/ip6_route.h>
++#include <net/ndisc.h>
++#include <net/mipglue.h>
+ EXPORT_SYMBOL(ipv6_addr_type);
+ EXPORT_SYMBOL(icmpv6_send);
+@@ -33,3 +35,48 @@ EXPORT_SYMBOL(inet6_ioctl);
+ EXPORT_SYMBOL(ipv6_get_saddr);
+ EXPORT_SYMBOL(ipv6_chk_addr);
+ EXPORT_SYMBOL(in6_dev_finish_destroy);
++
++#if defined(CONFIG_IPV6_TUNNEL_MODULE) || defined(CONFIG_IPV6_MOBILITY_MODULE)
++EXPORT_SYMBOL(ip6_build_xmit);
++EXPORT_SYMBOL(rt6_lookup);
++EXPORT_SYMBOL(ipv6_ext_hdr);
++#endif
++#ifdef CONFIG_IPV6_MOBILITY_MODULE
++EXPORT_SYMBOL(mipv6_functions);
++EXPORT_SYMBOL(mipv6_invalidate_calls);
++#if defined(CONFIG_IPV6_MOBILITY_HA_MODULE) || defined(CONFIG_IPV6_MOBILITY_MN_MODULE)
++EXPORT_SYMBOL(ip6_route_add);
++EXPORT_SYMBOL(ip6_route_del);
++EXPORT_SYMBOL(ipv6_get_lladdr);
++EXPORT_SYMBOL(ipv6_get_ifaddr);
++EXPORT_SYMBOL(nd_tbl);
++EXPORT_SYMBOL(ndisc_send_ns);
++EXPORT_SYMBOL(ndisc_send_na);
++EXPORT_SYMBOL(ndisc_next_option);
++EXPORT_SYMBOL(inet6_ifa_finish_destroy);
++#endif
++#ifdef CONFIG_IPV6_MOBILITY_HA_MODULE
++EXPORT_SYMBOL(ipv6_dev_ac_dec);
++EXPORT_SYMBOL(ipv6_dev_ac_inc);
++EXPORT_SYMBOL(ipv6_dev_mc_dec);
++EXPORT_SYMBOL(ipv6_dev_mc_inc);
++EXPORT_SYMBOL(ip6_forward);
++EXPORT_SYMBOL(ip6_input);
++EXPORT_SYMBOL(ipv6_chk_acast_addr);
++#endif
++#ifdef CONFIG_IPV6_MOBILITY_MN_MODULE
++#endif
++EXPORT_SYMBOL(addrconf_add_ifaddr);
++EXPORT_SYMBOL(addrconf_del_ifaddr);
++EXPORT_SYMBOL(addrconf_dad_start);
++EXPORT_SYMBOL(ip6_del_rt);
++EXPORT_SYMBOL(ip6_routing_table);
++EXPORT_SYMBOL(rt6_get_dflt_router);
++EXPORT_SYMBOL(rt6_purge_dflt_routers);
++EXPORT_SYMBOL(rt6_lock);
++EXPORT_SYMBOL(ndisc_send_rs);
++EXPORT_SYMBOL(fib6_clean_tree);
++EXPORT_SYMBOL(ipv6_del_addr);
++EXPORT_SYMBOL(ipv6_generate_eui64);
++EXPORT_SYMBOL(ipv6_inherit_eui64);
++#endif
+diff -uprN linux-2.4.25.old/net/ipv6/ipv6_tunnel.c linux-2.4.25/net/ipv6/ipv6_tunnel.c
+--- linux-2.4.25.old/net/ipv6/ipv6_tunnel.c    1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/ipv6_tunnel.c        2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,1604 @@
++/*
++ *    IPv6 over IPv6 tunnel device
++ *    Linux INET6 implementation
++ *
++ *    Authors:
++ *    Ville Nuorvala          <vnuorval@tcs.hut.fi>   
++ *
++ *    $Id$
++ *
++ *      Based on:
++ *      linux/net/ipv6/sit.c
++ *
++ *      RFC 2473
++ *
++ *    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 (at your option) any later version.
++ *
++ */
++
++#include <linux/config.h>
++#include <linux/module.h>
++#include <linux/errno.h>
++#include <linux/types.h>
++#include <linux/socket.h>
++#include <linux/sockios.h>
++#include <linux/if.h>
++#include <linux/in.h>
++#include <linux/ip.h>
++#include <linux/if_tunnel.h>
++#include <linux/net.h>
++#include <linux/in6.h>
++#include <linux/netdevice.h>
++#include <linux/if_arp.h>
++#include <linux/icmpv6.h>
++#include <linux/init.h>
++#include <linux/route.h>
++#include <linux/rtnetlink.h>
++#include <linux/tqueue.h>
++
++#include <asm/uaccess.h>
++#include <asm/atomic.h>
++
++#include <net/sock.h>
++#include <net/ipv6.h>
++#include <net/protocol.h>
++#include <net/ip6_route.h>
++#include <net/addrconf.h>
++#include <net/ipv6_tunnel.h>
++
++MODULE_AUTHOR("Ville Nuorvala");
++MODULE_DESCRIPTION("IPv6-in-IPv6 tunnel");
++MODULE_LICENSE("GPL");
++
++#define IPV6_TLV_TEL_DST_SIZE 8
++
++#ifdef IP6_TNL_DEBUG
++#define IP6_TNL_TRACE(x...) printk(KERN_DEBUG "%s:" x "\n", __FUNCTION__)
++#else
++#define IP6_TNL_TRACE(x...) do {;} while(0)
++#endif
++
++#define IPV6_TCLASS_MASK (IPV6_FLOWINFO_MASK & ~IPV6_FLOWLABEL_MASK)
++
++#define HASH_SIZE  32
++
++#define HASH(addr) (((addr)->s6_addr32[0] ^ (addr)->s6_addr32[1] ^ \
++                   (addr)->s6_addr32[2] ^ (addr)->s6_addr32[3]) & \
++                    (HASH_SIZE - 1))
++
++static int ip6ip6_fb_tnl_dev_init(struct net_device *dev);
++static int ip6ip6_tnl_dev_init(struct net_device *dev);
++
++/* the IPv6 IPv6 tunnel fallback device */
++static struct net_device ip6ip6_fb_tnl_dev = {
++      name: "ip6tnl0",
++      init: ip6ip6_fb_tnl_dev_init
++};
++
++/* the IPv6 IPv6 fallback tunnel */
++static struct ip6_tnl ip6ip6_fb_tnl = {
++      dev: &ip6ip6_fb_tnl_dev,
++      parms:{name: "ip6tnl0", proto: IPPROTO_IPV6}
++};
++
++/* lists for storing tunnels in use */
++static struct ip6_tnl *tnls_r_l[HASH_SIZE];
++static struct ip6_tnl *tnls_wc[1];
++static struct ip6_tnl **tnls[2] = { tnls_wc, tnls_r_l };
++
++/* list for unused cached kernel tunnels */
++static struct ip6_tnl *tnls_kernel[1];
++/* maximum number of cached kernel tunnels */
++static unsigned int max_kdev_count = 0;
++/* minimum number of cached kernel tunnels */
++static unsigned int min_kdev_count = 0;
++/* current number of cached kernel tunnels */
++static unsigned int kdev_count = 0;
++
++/* lists for tunnel hook functions */
++static struct list_head hooks[IP6_TNL_MAXHOOKS];
++
++/* locks for the different lists */
++static rwlock_t ip6ip6_lock = RW_LOCK_UNLOCKED;
++static rwlock_t ip6ip6_kernel_lock = RW_LOCK_UNLOCKED;
++static rwlock_t ip6ip6_hook_lock = RW_LOCK_UNLOCKED;
++
++/* flag indicating if the module is being removed */
++static int shutdown = 0;
++
++/**
++ * ip6ip6_tnl_lookup - fetch tunnel matching the end-point addresses
++ *   @remote: the address of the tunnel exit-point 
++ *   @local: the address of the tunnel entry-point 
++ *
++ * Return:  
++ *   tunnel matching given end-points if found,
++ *   else fallback tunnel if its device is up, 
++ *   else %NULL
++ **/
++
++struct ip6_tnl *
++ip6ip6_tnl_lookup(struct in6_addr *remote, struct in6_addr *local)
++{
++      unsigned h0 = HASH(remote);
++      unsigned h1 = HASH(local);
++      struct ip6_tnl *t;
++
++      for (t = tnls_r_l[h0 ^ h1]; t; t = t->next) {
++              if (!ipv6_addr_cmp(local, &t->parms.laddr) &&
++                  !ipv6_addr_cmp(remote, &t->parms.raddr) &&
++                  (t->dev->flags & IFF_UP))
++                      return t;
++      }
++      if ((t = tnls_wc[0]) != NULL && (t->dev->flags & IFF_UP))
++              return t;
++
++      return NULL;
++}
++
++/**
++ * ip6ip6_bucket - get head of list matching given tunnel parameters
++ *   @p: parameters containing tunnel end-points 
++ *
++ * Description:
++ *   ip6ip6_bucket() returns the head of the list matching the 
++ *   &struct in6_addr entries laddr and raddr in @p.
++ *
++ * Return: head of IPv6 tunnel list 
++ **/
++
++static struct ip6_tnl **
++ip6ip6_bucket(struct ip6_tnl_parm *p)
++{
++      struct in6_addr *remote = &p->raddr;
++      struct in6_addr *local = &p->laddr;
++      unsigned h = 0;
++      int prio = 0;
++
++      if (!ipv6_addr_any(remote) || !ipv6_addr_any(local)) {
++              prio = 1;
++              h = HASH(remote) ^ HASH(local);
++      }
++      return &tnls[prio][h];
++}
++
++/**
++ * ip6ip6_kernel_tnl_link - add new kernel tunnel to cache
++ *   @t: kernel tunnel
++ *
++ * Note:
++ *   %IP6_TNL_F_KERNEL_DEV is assumed to be raised in t->parms.flags. 
++ *   See the comments on ip6ip6_kernel_tnl_add() for more information.
++ **/
++
++static inline void
++ip6ip6_kernel_tnl_link(struct ip6_tnl *t)
++{
++      write_lock_bh(&ip6ip6_kernel_lock);
++      t->next = tnls_kernel[0];
++      tnls_kernel[0] = t;
++      kdev_count++;
++      write_unlock_bh(&ip6ip6_kernel_lock);
++}
++
++/**
++ * ip6ip6_kernel_tnl_unlink - remove first kernel tunnel from cache
++ *
++ * Return: first free kernel tunnel
++ *
++ * Note:
++ *   See the comments on ip6ip6_kernel_tnl_add() for more information.
++ **/
++
++static inline struct ip6_tnl *
++ip6ip6_kernel_tnl_unlink(void)
++{
++      struct ip6_tnl *t;
++
++      write_lock_bh(&ip6ip6_kernel_lock);
++      if ((t = tnls_kernel[0]) != NULL) {
++              tnls_kernel[0] = t->next;
++              kdev_count--;
++      }
++      write_unlock_bh(&ip6ip6_kernel_lock);
++      return t;
++}
++
++/**
++ * ip6ip6_tnl_link - add tunnel to hash table
++ *   @t: tunnel to be added
++ **/
++
++static void
++ip6ip6_tnl_link(struct ip6_tnl *t)
++{
++      struct ip6_tnl **tp = ip6ip6_bucket(&t->parms);
++
++      write_lock_bh(&ip6ip6_lock);
++      t->next = *tp;
++      *tp = t;
++      write_unlock_bh(&ip6ip6_lock);
++}
++
++/**
++ * ip6ip6_tnl_unlink - remove tunnel from hash table
++ *   @t: tunnel to be removed
++ **/
++
++static void
++ip6ip6_tnl_unlink(struct ip6_tnl *t)
++{
++      struct ip6_tnl **tp;
++      
++      write_lock_bh(&ip6ip6_lock);
++      for (tp = ip6ip6_bucket(&t->parms); *tp; tp = &(*tp)->next) {
++              if (t == *tp) {
++                      *tp = t->next;
++                      break;
++              }
++      }
++      write_unlock_bh(&ip6ip6_lock);
++}
++
++/**
++ * ip6ip6_tnl_create() - create a new tunnel
++ *   @p: tunnel parameters
++ *   @pt: pointer to new tunnel
++ *
++ * Description:
++ *   Create tunnel matching given parameters. New kernel managed devices are 
++ *   not put in the normal hash structure, but are instead cached for later
++ *   use.
++ * 
++ * Return: 
++ *   0 on success
++ **/
++
++
++static int __ip6ip6_tnl_create(struct ip6_tnl_parm *p,
++                             struct ip6_tnl **pt,
++                             int kernel_list)
++{
++      struct net_device *dev;
++      int err = -ENOBUFS;
++      struct ip6_tnl *t;
++
++      MOD_INC_USE_COUNT;
++      dev = kmalloc(sizeof (*dev) + sizeof (*t), GFP_KERNEL);
++      if (!dev) {
++              MOD_DEC_USE_COUNT;
++              return err;
++      }
++      memset(dev, 0, sizeof (*dev) + sizeof (*t));
++      dev->priv = (void *) (dev + 1);
++      t = (struct ip6_tnl *) dev->priv;
++      t->dev = dev;
++      dev->init = ip6ip6_tnl_dev_init;
++      dev->features |= NETIF_F_DYNALLOC;
++      if (kernel_list) {
++              memcpy(t->parms.name, p->name, IFNAMSIZ - 1);
++              t->parms.proto = IPPROTO_IPV6;
++              t->parms.flags = IP6_TNL_F_KERNEL_DEV;
++      } else {
++              memcpy(&t->parms, p, sizeof (*p));
++      }
++      t->parms.name[IFNAMSIZ - 1] = '\0';
++      strcpy(dev->name, t->parms.name);
++      if (!dev->name[0]) {
++              int i;
++              for (i = 0; i < IP6_TNL_MAX; i++) {
++                      sprintf(dev->name, "ip6tnl%d", i);
++                      if (__dev_get_by_name(dev->name) == NULL)
++                              break;
++              }
++
++              if (i == IP6_TNL_MAX) {
++                      goto failed;
++              }
++              memcpy(t->parms.name, dev->name, IFNAMSIZ);
++      }
++      if ((err = register_netdevice(dev)) < 0) {
++              goto failed;
++      }
++      dev_hold(dev);
++      if (kernel_list) {
++              ip6ip6_kernel_tnl_link(t);
++      } else {
++              ip6ip6_tnl_link(t);
++      }
++      *pt = t;
++      return 0;
++failed:
++      kfree(dev);
++      MOD_DEC_USE_COUNT;
++      return err;
++}
++
++
++int ip6ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt)
++{
++      return __ip6ip6_tnl_create(p, pt, 0);
++}
++
++
++static void manage_kernel_tnls(void *foo);
++
++static struct tq_struct manager_task = {
++      routine:manage_kernel_tnls,
++      data:NULL
++};
++
++/**
++ * manage_kernel_tnls() - create and destroy kernel tunnels
++ *
++ * Description:
++ *   manage_kernel_tnls() creates new kernel devices if there
++ *   are less than $min_kdev_count of them and deletes old ones if
++ *   there are less than $max_kdev_count of them in the cache
++ *
++ * Note:
++ *   Schedules itself to be run later in process context if called from 
++ *   interrupt. Therefore only works synchronously when called from process 
++ *   context.
++ **/
++
++static void
++manage_kernel_tnls(void *foo)
++{
++      struct ip6_tnl *t = NULL;
++      struct ip6_tnl_parm parm;
++
++      /* We can't do this processing in interrupt 
++         context so schedule it for later */
++      if (in_interrupt()) {
++              read_lock(&ip6ip6_kernel_lock);
++              if (!shutdown &&
++                  (kdev_count < min_kdev_count ||
++                   kdev_count > max_kdev_count)) {
++                      schedule_task(&manager_task);
++              }
++              read_unlock(&ip6ip6_kernel_lock);
++              return;
++      }
++
++      rtnl_lock();
++      read_lock_bh(&ip6ip6_kernel_lock);
++      memset(&parm, 0, sizeof (parm));
++      parm.flags = IP6_TNL_F_KERNEL_DEV;
++      /* Create tunnels until there are at least min_kdev_count */
++      while (kdev_count < min_kdev_count) {
++              read_unlock_bh(&ip6ip6_kernel_lock);
++              if (!__ip6ip6_tnl_create(&parm, &t, 1)) {
++                      dev_open(t->dev);
++              } else {
++                      goto err;
++              }
++              read_lock_bh(&ip6ip6_kernel_lock);
++      }
++
++      /* Destroy tunnels until there are at most max_kdev_count */
++      while (kdev_count > max_kdev_count) {
++              read_unlock_bh(&ip6ip6_kernel_lock);
++              if ((t = ip6ip6_kernel_tnl_unlink()) != NULL) {
++                      unregister_netdevice(t->dev);
++              } else {
++                      goto err;
++              }
++              read_lock_bh(&ip6ip6_kernel_lock);
++      }
++      read_unlock_bh(&ip6ip6_kernel_lock);
++err:
++      rtnl_unlock();
++}
++
++/**
++ * ip6ip6_tnl_inc_max_kdev_count() - increase max kernel dev cache size
++ *   @n: size increase
++ * Description:
++ *   Increase the upper limit for the number of kernel devices allowed in the 
++ *   cache at any on time.
++ **/
++
++unsigned int
++ip6ip6_tnl_inc_max_kdev_count(unsigned int n)
++{
++      write_lock_bh(&ip6ip6_kernel_lock);
++      max_kdev_count += n;
++      write_unlock_bh(&ip6ip6_kernel_lock);
++      manage_kernel_tnls(NULL);
++      return max_kdev_count;
++}
++
++/**
++ * ip6ip6_tnl_dec_max_kdev_count() -  decrease max kernel dev cache size
++ *   @n: size decrement
++ * Description:
++ *   Decrease the upper limit for the number of kernel devices allowed in the 
++ *   cache at any on time.
++ **/
++
++unsigned int
++ip6ip6_tnl_dec_max_kdev_count(unsigned int n)
++{
++      write_lock_bh(&ip6ip6_kernel_lock);
++      max_kdev_count -= min(max_kdev_count, n);
++      if (max_kdev_count < min_kdev_count)
++              min_kdev_count = max_kdev_count;
++      write_unlock_bh(&ip6ip6_kernel_lock);
++      manage_kernel_tnls(NULL);
++      return max_kdev_count;
++}
++
++/**
++ * ip6ip6_tnl_inc_min_kdev_count() - increase min kernel dev cache size
++ *   @n: size increase
++ * Description:
++ *   Increase the lower limit for the number of kernel devices allowed in the 
++ *   cache at any on time.
++ **/
++
++unsigned int
++ip6ip6_tnl_inc_min_kdev_count(unsigned int n)
++{
++      write_lock_bh(&ip6ip6_kernel_lock);
++      min_kdev_count += n;
++      if (min_kdev_count > max_kdev_count)
++              max_kdev_count = min_kdev_count;
++      write_unlock_bh(&ip6ip6_kernel_lock);
++      manage_kernel_tnls(NULL);
++      return min_kdev_count;
++}
++
++/**
++ * ip6ip6_tnl_dec_min_kdev_count() -  decrease min kernel dev cache size
++ *   @n: size decrement
++ * Description:
++ *   Decrease the lower limit for the number of kernel devices allowed in the 
++ *   cache at any on time.
++ **/
++
++unsigned int
++ip6ip6_tnl_dec_min_kdev_count(unsigned int n)
++{
++      write_lock_bh(&ip6ip6_kernel_lock);
++      min_kdev_count -= min(min_kdev_count, n);
++      write_unlock_bh(&ip6ip6_kernel_lock);
++      manage_kernel_tnls(NULL);
++      return min_kdev_count;
++}
++
++/**
++ * ip6ip6_tnl_locate - find or create tunnel matching given parameters
++ *   @p: tunnel parameters 
++ *   @create: != 0 if allowed to create new tunnel if no match found
++ *
++ * Description:
++ *   ip6ip6_tnl_locate() first tries to locate an existing tunnel
++ *   based on @parms. If this is unsuccessful, but @create is set a new
++ *   tunnel device is created and registered for use.
++ *
++ * Return:
++ *   0 if tunnel located or created,
++ *   -EINVAL if parameters incorrect,
++ *   -ENODEV if no matching tunnel available
++ **/
++
++int ip6ip6_tnl_locate(struct ip6_tnl_parm *p, struct ip6_tnl **pt, int create)
++{
++      struct in6_addr *remote = &p->raddr;
++      struct in6_addr *local = &p->laddr;
++      struct ip6_tnl *t;
++
++      if (p->proto != IPPROTO_IPV6)
++              return -EINVAL;
++
++      for (t = *ip6ip6_bucket(p); t; t = t->next) {
++              if (!ipv6_addr_cmp(local, &t->parms.laddr) &&
++                  !ipv6_addr_cmp(remote, &t->parms.raddr)) {
++                      *pt = t;
++                      return (create ? -EEXIST : 0);
++              }
++      }
++      return ip6ip6_tnl_create(p, pt);
++}
++
++/**
++ * ip6ip6_tnl_dev_destructor - tunnel device destructor
++ *   @dev: the device to be destroyed
++ **/
++
++static void
++ip6ip6_tnl_dev_destructor(struct net_device *dev)
++{
++      if (dev != &ip6ip6_fb_tnl_dev) {
++              MOD_DEC_USE_COUNT;
++      }
++}
++
++/**
++ * ip6ip6_tnl_dev_uninit - tunnel device uninitializer
++ *   @dev: the device to be destroyed
++ *   
++ * Description:
++ *   ip6ip6_tnl_dev_uninit() removes tunnel from its list
++ **/
++
++static void
++ip6ip6_tnl_dev_uninit(struct net_device *dev)
++{
++      struct ip6_tnl *t = (struct ip6_tnl *) dev->priv;
++
++      if (dev == &ip6ip6_fb_tnl_dev) {
++              write_lock_bh(&ip6ip6_lock);
++              tnls_wc[0] = NULL;
++              write_unlock_bh(&ip6ip6_lock);
++      } else {
++              ip6ip6_tnl_unlink(t);
++      }
++      sock_release(t->sock);
++      dev_put(dev);
++}
++
++/**
++ * parse_tvl_tnl_enc_lim - handle encapsulation limit option
++ *   @skb: received socket buffer
++ *
++ * Return: 
++ *   0 if none was found, 
++ *   else index to encapsulation limit
++ **/
++
++static __u16
++parse_tlv_tnl_enc_lim(struct sk_buff *skb, __u8 * raw)
++{
++      struct ipv6hdr *ipv6h = (struct ipv6hdr *) raw;
++      __u8 nexthdr = ipv6h->nexthdr;
++      __u16 off = sizeof (*ipv6h);
++
++      while (ipv6_ext_hdr(nexthdr) && nexthdr != NEXTHDR_NONE) {
++              __u16 optlen = 0;
++              struct ipv6_opt_hdr *hdr;
++              if (raw + off + sizeof (*hdr) > skb->data &&
++                  !pskb_may_pull(skb, raw - skb->data + off + sizeof (*hdr)))
++                      break;
++
++              hdr = (struct ipv6_opt_hdr *) (raw + off);
++              if (nexthdr == NEXTHDR_FRAGMENT) {
++                      struct frag_hdr *frag_hdr = (struct frag_hdr *) hdr;
++                      if (frag_hdr->frag_off)
++                              break;
++                      optlen = 8;
++              } else if (nexthdr == NEXTHDR_AUTH) {
++                      optlen = (hdr->hdrlen + 2) << 2;
++              } else {
++                      optlen = ipv6_optlen(hdr);
++              }
++              if (nexthdr == NEXTHDR_DEST) {
++                      __u16 i = off + 2;
++                      while (1) {
++                              struct ipv6_tlv_tnl_enc_lim *tel;
++
++                              /* No more room for encapsulation limit */
++                              if (i + sizeof (*tel) > off + optlen)
++                                      break;
++
++                              tel = (struct ipv6_tlv_tnl_enc_lim *) &raw[i];
++                              /* return index of option if found and valid */
++                              if (tel->type == IPV6_TLV_TNL_ENCAP_LIMIT &&
++                                  tel->length == 1)
++                                      return i;
++                              /* else jump to next option */
++                              if (tel->type)
++                                      i += tel->length + 2;
++                              else
++                                      i++;
++                      }
++              }
++              nexthdr = hdr->nexthdr;
++              off += optlen;
++      }
++      return 0;
++}
++
++/**
++ * ip6ip6_err - tunnel error handler
++ *
++ * Description:
++ *   ip6ip6_err() should handle errors in the tunnel according
++ *   to the specifications in RFC 2473.
++ **/
++
++void ip6ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
++                 int type, int code, int offset, __u32 info)
++{
++      struct ipv6hdr *ipv6h = (struct ipv6hdr *) skb->data;
++      struct ip6_tnl *t;
++      int rel_msg = 0;
++      int rel_type = ICMPV6_DEST_UNREACH;
++      int rel_code = ICMPV6_ADDR_UNREACH;
++      __u32 rel_info = 0;
++      __u16 len;
++
++      /* If the packet doesn't contain the original IPv6 header we are 
++         in trouble since we might need the source address for furter 
++         processing of the error. */
++
++      read_lock(&ip6ip6_lock);
++      if ((t = ip6ip6_tnl_lookup(&ipv6h->daddr, &ipv6h->saddr)) == NULL)
++              goto out;
++
++      switch (type) {
++              __u32 teli;
++              struct ipv6_tlv_tnl_enc_lim *tel;
++              __u32 mtu;
++      case ICMPV6_DEST_UNREACH:
++              if (net_ratelimit())
++                      printk(KERN_WARNING
++                             "%s: Path to destination invalid "
++                             "or inactive!\n", t->parms.name);
++              rel_msg = 1;
++              break;
++      case ICMPV6_TIME_EXCEED:
++              if (code == ICMPV6_EXC_HOPLIMIT) {
++                      if (net_ratelimit())
++                              printk(KERN_WARNING
++                                     "%s: Too small hop limit or "
++                                     "routing loop in tunnel!\n", 
++                                     t->parms.name);
++                      rel_msg = 1;
++              }
++              break;
++      case ICMPV6_PARAMPROB:
++              /* ignore if parameter problem not caused by a tunnel
++                 encapsulation limit sub-option */
++              if (code != ICMPV6_HDR_FIELD) {
++                      break;
++              }
++              teli = parse_tlv_tnl_enc_lim(skb, skb->data);
++
++              if (teli && teli == ntohl(info) - 2) {
++                      tel = (struct ipv6_tlv_tnl_enc_lim *) &skb->data[teli];
++                      if (tel->encap_limit == 0) {
++                              if (net_ratelimit())
++                                      printk(KERN_WARNING
++                                             "%s: Too small encapsulation "
++                                             "limit or routing loop in "
++                                             "tunnel!\n", t->parms.name);
++                              rel_msg = 1;
++                      }
++              }
++              break;
++      case ICMPV6_PKT_TOOBIG:
++              mtu = ntohl(info) - offset;
++              if (mtu < IPV6_MIN_MTU)
++                      mtu = IPV6_MIN_MTU;
++              t->dev->mtu = mtu;
++
++              if ((len = sizeof (*ipv6h) + ipv6h->payload_len) > mtu) {
++                      rel_type = ICMPV6_PKT_TOOBIG;
++                      rel_code = 0;
++                      rel_info = mtu;
++                      rel_msg = 1;
++              }
++              break;
++      }
++      if (rel_msg && pskb_may_pull(skb, offset + sizeof (*ipv6h))) {
++              struct rt6_info *rt;
++              struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
++              if (!skb2)
++                      goto out;
++
++              dst_release(skb2->dst);
++              skb2->dst = NULL;
++              skb_pull(skb2, offset);
++              skb2->nh.raw = skb2->data;
++
++              /* Try to guess incoming interface */
++              rt = rt6_lookup(&skb2->nh.ipv6h->saddr, NULL, 0, 0);
++
++              if (rt && rt->rt6i_dev)
++                      skb2->dev = rt->rt6i_dev;
++
++              icmpv6_send(skb2, rel_type, rel_code, rel_info, skb2->dev);
++
++              if (rt)
++                      dst_release(&rt->u.dst);
++
++              kfree_skb(skb2);
++      }
++out:
++      read_unlock(&ip6ip6_lock);
++}
++
++/**
++ * call_hooks - call ipv6 tunnel hooks
++ *   @hooknum: hook number, either %IP6_TNL_PRE_ENCAP, or 
++ *   %IP6_TNL_PRE_DECAP
++ *   @t: the current tunnel
++ *   @skb: the tunneled packet
++ *
++ * Description:
++ *   Pass packet to all the hook functions until %IP6_TNL_DROP
++ *
++ * Return:
++ *   %IP6_TNL_ACCEPT or %IP6_TNL_DROP
++ **/
++
++static inline int
++call_hooks(unsigned int hooknum, struct ip6_tnl *t, struct sk_buff *skb)
++{
++      struct ip6_tnl_hook_ops *h;
++      int accept = IP6_TNL_ACCEPT;
++
++      if (hooknum < IP6_TNL_MAXHOOKS) {
++              struct list_head *i;
++              read_lock(&ip6ip6_hook_lock);
++              for (i = hooks[hooknum].next; i != &hooks[hooknum]; i = i->next) {
++                      h = (struct ip6_tnl_hook_ops *) i;
++
++                      if (h->hook) {
++                              accept = h->hook(t, skb);
++
++                              if (accept != IP6_TNL_ACCEPT)
++                                      break;
++                      }
++              }
++              read_unlock(&ip6ip6_hook_lock);
++      }
++      return accept;
++}
++
++/**
++ * ip6ip6_rcv - decapsulate IPv6 packet and retransmit it locally
++ *   @skb: received socket buffer
++ *
++ * Return: 0
++ **/
++
++int ip6ip6_rcv(struct sk_buff *skb)
++{
++      struct ipv6hdr *ipv6h;
++      struct ip6_tnl *t;
++
++      if (!pskb_may_pull(skb, sizeof (*ipv6h)))
++              goto discard;
++
++      ipv6h = skb->nh.ipv6h;
++
++      read_lock(&ip6ip6_lock);
++
++      if ((t = ip6ip6_tnl_lookup(&ipv6h->saddr, &ipv6h->daddr)) != NULL) {
++              if (!(t->parms.flags & IP6_TNL_F_CAP_RCV) ||
++                  call_hooks(IP6_TNL_PRE_DECAP, t, skb) != IP6_TNL_ACCEPT) {
++                      t->stat.rx_dropped++;
++                      read_unlock(&ip6ip6_lock);
++                      goto discard;
++              }
++              skb->mac.raw = skb->nh.raw;
++              skb->nh.raw = skb->data;
++              skb->protocol = htons(ETH_P_IPV6);
++              skb->pkt_type = PACKET_HOST;
++              memset(skb->cb, 0, sizeof(struct inet6_skb_parm));
++              skb->dev = t->dev;
++              dst_release(skb->dst);
++              skb->dst = NULL;
++              t->stat.rx_packets++;
++              t->stat.rx_bytes += skb->len;
++              netif_rx(skb);
++              read_unlock(&ip6ip6_lock);
++              return 0;
++      }
++      read_unlock(&ip6ip6_lock);
++      icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH, 0, skb->dev);
++discard:
++      kfree_skb(skb);
++      return 0;
++}
++
++static inline struct ipv6_txoptions *create_tel(__u8 encap_limit)
++{
++      struct ipv6_tlv_tnl_enc_lim *tel;
++      struct ipv6_txoptions *opt;
++      __u8 *raw;
++
++      int opt_len = sizeof(*opt) + IPV6_TLV_TEL_DST_SIZE;
++
++      if (!(opt = kmalloc(opt_len, GFP_ATOMIC))) {
++              return NULL;
++      }
++      memset(opt, 0, opt_len);
++      opt->tot_len = opt_len;
++      opt->dst0opt = (struct ipv6_opt_hdr *) (opt + 1);
++      opt->opt_nflen = 8;
++
++      tel = (struct ipv6_tlv_tnl_enc_lim *) (opt->dst0opt + 1);
++      tel->type = IPV6_TLV_TNL_ENCAP_LIMIT;
++      tel->length = 1;
++      tel->encap_limit = encap_limit;
++
++      raw = (__u8 *) opt->dst0opt;
++      raw[5] = IPV6_TLV_PADN;
++      raw[6] = 1;
++
++      return opt;
++}
++
++static int
++ip6ip6_getfrag(const void *data, struct in6_addr *addr,
++                char *buff, unsigned int offset, unsigned int len)
++{
++      memcpy(buff, data + offset, len);
++      return 0;
++}
++
++/**
++ * ip6ip6_tnl_addr_conflict - compare packet addresses to tunnel's own
++ *   @t: the outgoing tunnel device
++ *   @hdr: IPv6 header from the incoming packet 
++ *
++ * Description:
++ *   Avoid trivial tunneling loop by checking that tunnel exit-point 
++ *   doesn't match source of incoming packet.
++ *
++ * Return: 
++ *   1 if conflict,
++ *   0 else
++ **/
++
++static inline int
++ip6ip6_tnl_addr_conflict(struct ip6_tnl *t, struct ipv6hdr *hdr)
++{
++      return !ipv6_addr_cmp(&t->parms.raddr, &hdr->saddr);
++}
++
++/**
++ * ip6ip6_tnl_xmit - encapsulate packet and send 
++ *   @skb: the outgoing socket buffer
++ *   @dev: the outgoing tunnel device 
++ *
++ * Description:
++ *   Build new header and do some sanity checks on the packet before sending
++ *   it to ip6_build_xmit().
++ *
++ * Return: 
++ *   0
++ **/
++
++int ip6ip6_tnl_xmit(struct sk_buff *skb, struct net_device *dev)
++{
++      struct ip6_tnl *t = (struct ip6_tnl *) dev->priv;
++      struct net_device_stats *stats = &t->stat;
++      struct ipv6hdr *ipv6h = skb->nh.ipv6h;
++      struct ipv6_txoptions *opt = NULL;
++      int encap_limit = -1;
++      __u16 offset;
++      struct flowi fl;
++      int err = 0;
++      struct dst_entry *dst;
++      struct sock *sk = t->sock->sk;
++      struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
++      int mtu;
++
++      if (t->recursion++) {
++              stats->collisions++;
++              goto tx_err;
++      }
++      if (skb->protocol != htons(ETH_P_IPV6) ||
++          !(t->parms.flags & IP6_TNL_F_CAP_XMIT) ||
++          ip6ip6_tnl_addr_conflict(t, ipv6h)) {
++              goto tx_err;
++      }
++      if ((offset = parse_tlv_tnl_enc_lim(skb, skb->nh.raw)) > 0) {
++              struct ipv6_tlv_tnl_enc_lim *tel;
++              tel = (struct ipv6_tlv_tnl_enc_lim *) &skb->nh.raw[offset];
++              if (tel->encap_limit == 0) {
++                      icmpv6_send(skb, ICMPV6_PARAMPROB,
++                                  ICMPV6_HDR_FIELD, offset + 2, skb->dev);
++                      goto tx_err;
++              }
++              encap_limit = tel->encap_limit - 1;
++      } else if (!(t->parms.flags & IP6_TNL_F_IGN_ENCAP_LIMIT)) {
++              encap_limit = t->parms.encap_limit;
++      }
++      if (call_hooks(IP6_TNL_PRE_ENCAP, t, skb) != IP6_TNL_ACCEPT)
++              goto discard;
++      memcpy(&fl, &t->fl, sizeof (fl));
++
++      if ((t->parms.flags & IP6_TNL_F_USE_ORIG_TCLASS))
++              fl.fl6_flowlabel |= (*(__u32 *) ipv6h & IPV6_TCLASS_MASK);
++      if ((t->parms.flags & IP6_TNL_F_USE_ORIG_FLOWLABEL))
++              fl.fl6_flowlabel |= (*(__u32 *) ipv6h & IPV6_FLOWLABEL_MASK);
++
++      if (encap_limit >= 0 && (opt = create_tel(encap_limit)) == NULL)
++              goto tx_err;
++
++      dst = __sk_dst_check(sk, np->dst_cookie);
++
++      if (dst) {
++              if (np->daddr_cache == NULL ||
++                  ipv6_addr_cmp(fl.fl6_dst, np->daddr_cache) ||
++#ifdef CONFIG_IPV6_SUBTREES
++                  np->saddr_cache == NULL ||
++                  ipv6_addr_cmp(fl.fl6_src, np->saddr_cache) ||
++#endif
++                  (fl.oif && fl.oif != dst->dev->ifindex)) {
++                      dst = NULL;
++              } else {
++                      dst_hold(dst);
++              }
++      }
++      if (dst == NULL) {
++              dst = ip6_route_output(sk, &fl);
++              if (dst->error) {
++                      stats->tx_carrier_errors++;
++                      dst_link_failure(skb);
++                      goto tx_err_dst_release;
++              }
++              /* local routing loop */
++              if (dst->dev == dev) {
++                      stats->collisions++;
++                      if (net_ratelimit())
++                              printk(KERN_WARNING 
++                                     "%s: Local routing loop detected!\n",
++                                     t->parms.name);
++                      goto tx_err_dst_release;
++              }
++      }
++      mtu = dst->pmtu - sizeof (*ipv6h);
++      if (opt) {
++              mtu -= (opt->opt_nflen + opt->opt_flen);
++      }
++      if (mtu < IPV6_MIN_MTU)
++              mtu = IPV6_MIN_MTU;
++      if (skb->dst && mtu < skb->dst->pmtu) {
++              struct rt6_info *rt = (struct rt6_info *) skb->dst;
++              rt->rt6i_flags |= RTF_MODIFIED;
++              rt->u.dst.pmtu = mtu;
++      }
++      if (skb->len > mtu) {
++              icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, dev);
++              goto tx_err_dst_release;
++      }
++      ip6_dst_store(sk, dst, &np->daddr, &np->saddr);
++      err = ip6_build_xmit(sk, ip6ip6_getfrag, (void *) skb->nh.raw,
++                           &fl, skb->len, opt, t->parms.hop_limit,
++                           MSG_DONTWAIT);
++
++      if (err == NET_XMIT_SUCCESS || err == NET_XMIT_CN) {
++              stats->tx_bytes += skb->len;
++              stats->tx_packets++;
++      } else {
++              stats->tx_errors++;
++              stats->tx_aborted_errors++;
++      }
++      if (opt)
++              kfree(opt);
++      kfree_skb(skb);
++      t->recursion--;
++      return 0;
++tx_err_dst_release:
++      dst_release(dst);
++      if (opt)
++              kfree(opt);
++tx_err:
++      stats->tx_errors++;
++discard:
++      stats->tx_dropped++;
++      kfree_skb(skb);
++      t->recursion--;
++      return 0;
++}
++
++static void ip6_tnl_set_cap(struct ip6_tnl *t)
++{
++      struct ip6_tnl_parm *p = &t->parms;
++      struct in6_addr *laddr = &p->laddr;
++      struct in6_addr *raddr = &p->raddr;
++      int ltype = ipv6_addr_type(laddr);
++      int rtype = ipv6_addr_type(raddr);
++
++      p->flags &= ~(IP6_TNL_F_CAP_XMIT|IP6_TNL_F_CAP_RCV);
++
++      if (ltype != IPV6_ADDR_ANY && rtype != IPV6_ADDR_ANY &&
++          ((ltype|rtype) &
++           (IPV6_ADDR_UNICAST|
++            IPV6_ADDR_LOOPBACK|IPV6_ADDR_LINKLOCAL|
++            IPV6_ADDR_MAPPED|IPV6_ADDR_RESERVED)) == IPV6_ADDR_UNICAST) {
++              struct net_device *ldev = NULL;
++              int l_ok = 1;
++              int r_ok = 1;
++
++              if (p->link)
++                      ldev = dev_get_by_index(p->link);
++
++              if ((ltype&IPV6_ADDR_UNICAST) && !ipv6_chk_addr(laddr, ldev))
++                      l_ok = 0;
++
++              if ((rtype&IPV6_ADDR_UNICAST) && ipv6_chk_addr(raddr, NULL))
++                      r_ok = 0;
++
++              if (l_ok && r_ok) {
++                      if (ltype&IPV6_ADDR_UNICAST)
++                              p->flags |= IP6_TNL_F_CAP_XMIT;
++                      if (rtype&IPV6_ADDR_UNICAST)
++                              p->flags |= IP6_TNL_F_CAP_RCV;
++              }
++              if (ldev)
++                      dev_put(ldev);
++      }
++}
++
++static void ip6ip6_tnl_link_config(struct ip6_tnl *t)
++{
++      struct net_device *dev = t->dev;
++      struct ip6_tnl_parm *p = &t->parms;
++      struct flowi *fl = &t->fl;
++
++      /* Set up flowi template */
++      fl->fl6_src = &p->laddr;
++      fl->fl6_dst = &p->raddr;
++      fl->oif = p->link;
++      fl->fl6_flowlabel = 0;
++
++      if (!(p->flags&IP6_TNL_F_USE_ORIG_TCLASS))
++              fl->fl6_flowlabel |= IPV6_TCLASS_MASK & htonl(p->flowinfo);
++      if (!(p->flags&IP6_TNL_F_USE_ORIG_FLOWLABEL))
++              fl->fl6_flowlabel |= IPV6_FLOWLABEL_MASK & htonl(p->flowinfo);
++
++      ip6_tnl_set_cap(t);
++
++      if (p->flags&IP6_TNL_F_CAP_XMIT && p->flags&IP6_TNL_F_CAP_RCV)
++              dev->flags |= IFF_POINTOPOINT;
++      else
++              dev->flags &= ~IFF_POINTOPOINT;
++
++      if (p->flags & IP6_TNL_F_CAP_XMIT) {
++              struct rt6_info *rt = rt6_lookup(&p->raddr, &p->laddr,
++                                               p->link, 0);
++              
++              if (rt == NULL)
++                      return;
++              
++              if (rt->rt6i_dev) {
++                      dev->iflink = rt->rt6i_dev->ifindex;
++
++                      dev->hard_header_len = rt->rt6i_dev->hard_header_len +
++                              sizeof (struct ipv6hdr);
++
++                      dev->mtu = rt->rt6i_dev->mtu - sizeof (struct ipv6hdr);
++
++                      if (dev->mtu < IPV6_MIN_MTU)
++                              dev->mtu = IPV6_MIN_MTU;
++              }
++              dst_release(&rt->u.dst);
++      }
++}
++
++/**
++ * __ip6ip6_tnl_change - update the tunnel parameters
++ *   @t: tunnel to be changed
++ *   @p: tunnel configuration parameters
++ *
++ * Description:
++ *   __ip6ip6_tnl_change() updates the tunnel parameters
++ **/
++
++static void
++__ip6ip6_tnl_change(struct ip6_tnl *t, struct ip6_tnl_parm *p)
++{
++      ipv6_addr_copy(&t->parms.laddr, &p->laddr);
++      ipv6_addr_copy(&t->parms.raddr, &p->raddr);
++      t->parms.flags = p->flags;
++      t->parms.hop_limit = p->hop_limit;
++      t->parms.encap_limit = p->encap_limit;
++      t->parms.flowinfo = p->flowinfo;
++      ip6ip6_tnl_link_config(t);
++}
++
++void ip6ip6_tnl_change(struct ip6_tnl *t, struct ip6_tnl_parm *p)
++{
++      ip6ip6_tnl_unlink(t);
++      __ip6ip6_tnl_change(t, p);
++      ip6ip6_tnl_link(t);
++}
++
++/**
++ * ip6ip6_kernel_tnl_add - configure and add kernel tunnel to hash 
++ *   @p: kernel tunnel configuration parameters
++ *
++ * Description:
++ *   ip6ip6_kernel_tnl_add() fetches an unused kernel tunnel configures
++ *   it according to @p and places it among the active tunnels.
++ * 
++ * Return:
++ *   number of references to tunnel on success,
++ *   %-EEXIST if there is already a device matching description
++ *   %-EINVAL if p->flags doesn't have %IP6_TNL_F_KERNEL_DEV raised,
++ *   %-ENODEV if there are no unused kernel tunnels available 
++ * 
++ * Note:
++ *   The code for creating, opening, closing and destroying network devices
++ *   must be called from process context, while the Mobile IP code, which 
++ *   needs the tunnel devices, unfortunately runs in interrupt context. 
++ *   
++ *   The devices must be created and opened in advance, then placed in a 
++ *   list where the kernel can fetch and ready them for use at a later time.
++ *
++ **/
++
++int
++ip6ip6_kernel_tnl_add(struct ip6_tnl_parm *p)
++{
++      struct ip6_tnl *t;
++
++      if (!(p->flags & IP6_TNL_F_KERNEL_DEV))
++              return -EINVAL;
++      if ((t = ip6ip6_tnl_lookup(&p->raddr, &p->laddr)) != NULL &&
++          t != &ip6ip6_fb_tnl) {
++              /* Handle duplicate tunnels by incrementing 
++                 reference count */
++              atomic_inc(&t->refcnt);
++              goto out;
++      }
++      if ((t = ip6ip6_kernel_tnl_unlink()) == NULL)
++              return -ENODEV;
++      __ip6ip6_tnl_change(t, p);
++
++      atomic_inc(&t->refcnt);
++
++      ip6ip6_tnl_link(t);
++
++      manage_kernel_tnls(NULL);
++out:
++      return atomic_read(&t->refcnt);
++}
++
++/**
++ * ip6ip6_kernel_tnl_del - delete no longer needed kernel tunnel 
++ *   @t: kernel tunnel to be removed from hash
++ *
++ * Description:
++ *   ip6ip6_kernel_tnl_del() removes and deconfigures the tunnel @t
++ *   and places it among the unused kernel devices.
++ * 
++ * Return:
++ *   number of references on success,
++ *   %-EINVAL if p->flags doesn't have %IP6_TNL_F_KERNEL_DEV raised,
++ * 
++ * Note:
++ *   See the comments on ip6ip6_kernel_tnl_add() for more information.
++ **/
++
++int
++ip6ip6_kernel_tnl_del(struct ip6_tnl *t)
++{
++      if (!t)
++              return -ENODEV;
++
++      if (!(t->parms.flags & IP6_TNL_F_KERNEL_DEV))
++              return -EINVAL;
++
++      if (atomic_dec_and_test(&t->refcnt)) {
++              struct ip6_tnl_parm p;
++              ip6ip6_tnl_unlink(t);
++              memset(&p, 0, sizeof (p));
++              p.flags = IP6_TNL_F_KERNEL_DEV;
++
++              __ip6ip6_tnl_change(t, &p);
++
++              ip6ip6_kernel_tnl_link(t);
++
++              manage_kernel_tnls(NULL);
++      }
++      return atomic_read(&t->refcnt);
++}
++
++/**
++ * ip6ip6_tnl_ioctl - configure ipv6 tunnels from userspace 
++ *   @dev: virtual device associated with tunnel
++ *   @ifr: parameters passed from userspace
++ *   @cmd: command to be performed
++ *
++ * Description:
++ *   ip6ip6_tnl_ioctl() is used for managing IPv6 tunnels 
++ *   from userspace. 
++ *
++ *   The possible commands are the following:
++ *     %SIOCGETTUNNEL: get tunnel parameters for device
++ *     %SIOCADDTUNNEL: add tunnel matching given tunnel parameters
++ *     %SIOCCHGTUNNEL: change tunnel parameters to those given
++ *     %SIOCDELTUNNEL: delete tunnel
++ *
++ *   The fallback device "ip6tnl0", created during module 
++ *   initialization, can be used for creating other tunnel devices.
++ *
++ * Return:
++ *   0 on success,
++ *   %-EFAULT if unable to copy data to or from userspace,
++ *   %-EPERM if current process hasn't %CAP_NET_ADMIN set or attempting
++ *   to configure kernel devices from userspace, 
++ *   %-EINVAL if passed tunnel parameters are invalid,
++ *   %-EEXIST if changing a tunnel's parameters would cause a conflict
++ *   %-ENODEV if attempting to change or delete a nonexisting device
++ *
++ * Note:
++ *   See the comments on ip6ip6_kernel_tnl_add() for more information 
++ *   about kernel tunnels.
++ * **/
++
++static int
++ip6ip6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
++{
++      int err = 0;
++      int create;
++      struct ip6_tnl_parm p;
++      struct ip6_tnl *t = NULL;
++
++      MOD_INC_USE_COUNT;
++
++      switch (cmd) {
++      case SIOCGETTUNNEL:
++              if (dev == &ip6ip6_fb_tnl_dev) {
++                      if (copy_from_user(&p,
++                                         ifr->ifr_ifru.ifru_data,
++                                         sizeof (p))) {
++                              err = -EFAULT;
++                              break;
++                      }
++                      if ((err = ip6ip6_tnl_locate(&p, &t, 0)) == -ENODEV)
++                              t = (struct ip6_tnl *) dev->priv;
++                      else if (err)
++                              break;
++              } else
++                      t = (struct ip6_tnl *) dev->priv;
++
++              memcpy(&p, &t->parms, sizeof (p));
++              if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof (p))) {
++                      err = -EFAULT;
++              }
++              break;
++      case SIOCADDTUNNEL:
++      case SIOCCHGTUNNEL:
++              err = -EPERM;
++              create = (cmd == SIOCADDTUNNEL);
++              if (!capable(CAP_NET_ADMIN))
++                      break;
++              if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof (p))) {
++                      err = -EFAULT;
++                      break;
++              }
++              if (p.flags & IP6_TNL_F_KERNEL_DEV) {
++                      break;
++              }
++              if (!create && dev != &ip6ip6_fb_tnl_dev) {
++                      t = (struct ip6_tnl *) dev->priv;
++              }
++              if (!t && (err = ip6ip6_tnl_locate(&p, &t, create))) {
++                      break;
++              }
++              if (cmd == SIOCCHGTUNNEL) {
++                      if (t->dev != dev) {
++                              err = -EEXIST;
++                              break;
++                      }
++                      if (t->parms.flags & IP6_TNL_F_KERNEL_DEV) {
++                              err = -EPERM;
++                              break;
++                      }
++                      ip6ip6_tnl_change(t, &p);
++                      netdev_state_change(dev);
++              }
++              if (copy_to_user(ifr->ifr_ifru.ifru_data,
++                               &t->parms, sizeof (p))) {
++                      err = -EFAULT;
++              } else {
++                      err = 0;
++              }
++              break;
++      case SIOCDELTUNNEL:
++              err = -EPERM;
++              if (!capable(CAP_NET_ADMIN))
++                      break;
++
++              if (dev == &ip6ip6_fb_tnl_dev) {
++                      if (copy_from_user(&p, ifr->ifr_ifru.ifru_data,
++                                         sizeof (p))) {
++                              err = -EFAULT;
++                              break;
++                      }
++                      err = ip6ip6_tnl_locate(&p, &t, 0);
++                      if (err)
++                              break;
++                      if (t == &ip6ip6_fb_tnl) {
++                              err = -EPERM;
++                              break;
++                      }
++              } else {
++                      t = (struct ip6_tnl *) dev->priv;
++              }
++              if (t->parms.flags & IP6_TNL_F_KERNEL_DEV)
++                      err = -EPERM;
++              else
++                      err = unregister_netdevice(t->dev);
++              break;
++      default:
++              err = -EINVAL;
++      }
++      MOD_DEC_USE_COUNT;
++      return err;
++}
++
++/**
++ * ip6ip6_tnl_get_stats - return the stats for tunnel device 
++ *   @dev: virtual device associated with tunnel
++ *
++ * Return: stats for device
++ **/
++
++static struct net_device_stats *
++ip6ip6_tnl_get_stats(struct net_device *dev)
++{
++      return &(((struct ip6_tnl *) dev->priv)->stat);
++}
++
++/**
++ * ip6ip6_tnl_change_mtu - change mtu manually for tunnel device
++ *   @dev: virtual device associated with tunnel
++ *   @new_mtu: the new mtu
++ *
++ * Return:
++ *   0 on success,
++ *   %-EINVAL if mtu too small
++ **/
++
++static int
++ip6ip6_tnl_change_mtu(struct net_device *dev, int new_mtu)
++{
++      if (new_mtu < IPV6_MIN_MTU) {
++              return -EINVAL;
++      }
++      dev->mtu = new_mtu;
++      return 0;
++}
++
++/**
++ * ip6ip6_tnl_dev_init_gen - general initializer for all tunnel devices
++ *   @dev: virtual device associated with tunnel
++ *
++ * Description:
++ *   Set function pointers and initialize the &struct flowi template used
++ *   by the tunnel.
++ **/
++
++static int
++ip6ip6_tnl_dev_init_gen(struct net_device *dev)
++{
++      struct ip6_tnl *t = (struct ip6_tnl *) dev->priv;
++      struct flowi *fl = &t->fl;
++      int err;
++      struct sock *sk;
++
++      if ((err = sock_create(PF_INET6, SOCK_RAW, IPPROTO_IPV6, &t->sock))) {
++              printk(KERN_ERR
++                     "Failed to create IPv6 tunnel socket (err %d).\n", err);
++              return err;
++      }
++      t->sock->inode->i_uid = 0;
++      t->sock->inode->i_gid = 0;
++
++      sk = t->sock->sk;
++      sk->allocation = GFP_ATOMIC;
++      sk->net_pinfo.af_inet6.hop_limit = 254;
++      sk->net_pinfo.af_inet6.mc_loop = 0;
++      sk->prot->unhash(sk);
++
++      memset(fl, 0, sizeof (*fl));
++      fl->proto = IPPROTO_IPV6;
++
++      dev->destructor = ip6ip6_tnl_dev_destructor;
++      dev->uninit = ip6ip6_tnl_dev_uninit;
++      dev->hard_start_xmit = ip6ip6_tnl_xmit;
++      dev->get_stats = ip6ip6_tnl_get_stats;
++      dev->do_ioctl = ip6ip6_tnl_ioctl;
++      dev->change_mtu = ip6ip6_tnl_change_mtu;
++
++      dev->type = ARPHRD_TUNNEL6;
++      dev->hard_header_len = LL_MAX_HEADER + sizeof (struct ipv6hdr);
++      dev->mtu = ETH_DATA_LEN - sizeof (struct ipv6hdr);
++      dev->flags |= IFF_NOARP;
++      dev->iflink = 0;
++      /* Hmm... MAX_ADDR_LEN is 8, so the ipv6 addresses can't be 
++         copied to dev->dev_addr and dev->broadcast, like the ipv4
++         addresses were in ipip.c, ip_gre.c and sit.c. */
++      dev->addr_len = 0;
++      return 0;
++}
++
++/**
++ * ip6ip6_tnl_dev_init - initializer for all non fallback tunnel devices
++ *   @dev: virtual device associated with tunnel
++ **/
++
++static int
++ip6ip6_tnl_dev_init(struct net_device *dev)
++{
++      struct ip6_tnl *t = (struct ip6_tnl *) dev->priv;
++      ip6ip6_tnl_dev_init_gen(dev);
++      ip6ip6_tnl_link_config(t);
++      return 0;
++}
++
++#ifdef MODULE
++
++/**
++ * ip6ip6_fb_tnl_open - function called when fallback device opened
++ *   @dev: fallback device
++ *
++ * Return: 0 
++ **/
++
++static int
++ip6ip6_fb_tnl_open(struct net_device *dev)
++{
++      MOD_INC_USE_COUNT;
++      return 0;
++}
++
++/**
++ * ip6ip6_fb_tnl_close - function called when fallback device closed
++ *   @dev: fallback device
++ *
++ * Return: 0 
++ **/
++
++static int
++ip6ip6_fb_tnl_close(struct net_device *dev)
++{
++      MOD_DEC_USE_COUNT;
++      return 0;
++}
++#endif
++
++/**
++ * ip6ip6_fb_tnl_dev_init - initializer for fallback tunnel device
++ *   @dev: fallback device
++ *
++ * Return: 0
++ **/
++
++int __init
++ip6ip6_fb_tnl_dev_init(struct net_device *dev)
++{
++      ip6ip6_tnl_dev_init_gen(dev);
++#ifdef MODULE
++      dev->open = ip6ip6_fb_tnl_open;
++      dev->stop = ip6ip6_fb_tnl_close;
++#endif
++      dev_hold(dev);
++      tnls_wc[0] = &ip6ip6_fb_tnl;
++      return 0;
++}
++
++/**
++ * ip6ip6_tnl_register_hook - add hook for processing of tunneled packets
++ *   @reg: hook function and its parameters
++ * 
++ * Description:
++ *   Add a netfilter like hook function for special handling of tunneled 
++ *   packets. The hook functions are called before encapsulation 
++ *   (%IP6_TNL_PRE_ENCAP) and before decapsulation 
++ *   (%IP6_TNL_PRE_DECAP). The possible return values by the hook 
++ *   functions are %IP6_TNL_DROP, %IP6_TNL_ACCEPT and 
++ *   %IP6_TNL_STOLEN (in case the hook function took care of the packet
++ *   and it doesn't have to be processed any further).
++ **/
++
++void
++ip6ip6_tnl_register_hook(struct ip6_tnl_hook_ops *reg)
++{
++      if (reg->hooknum < IP6_TNL_MAXHOOKS) {
++              struct list_head *i;
++
++              write_lock_bh(&ip6ip6_hook_lock);
++              for (i = hooks[reg->hooknum].next;
++                   i != &hooks[reg->hooknum]; i = i->next) {
++                      if (reg->priority <
++                          ((struct ip6_tnl_hook_ops *) i)->priority) {
++                              break;
++                      }
++              }
++              list_add(&reg->list, i->prev);
++              write_unlock_bh(&ip6ip6_hook_lock);
++      }
++}
++
++/**
++ * ip6ip6_tnl_unregister_hook - remove tunnel hook
++ *   @reg: hook function and its parameters
++ **/
++
++void
++ip6ip6_tnl_unregister_hook(struct ip6_tnl_hook_ops *reg)
++{
++      if (reg->hooknum < IP6_TNL_MAXHOOKS) {
++              write_lock_bh(&ip6ip6_hook_lock);
++              list_del(&reg->list);
++              write_unlock_bh(&ip6ip6_hook_lock);
++      }
++}
++
++
++/* the IPv6 over IPv6 protocol structure */
++static struct inet6_protocol ip6ip6_protocol = {
++      ip6ip6_rcv,             /* IPv6 handler         */
++      ip6ip6_err,             /* IPv6 error control   */
++      NULL,                   /* next                 */
++      IPPROTO_IPV6,           /* protocol ID          */
++      0,                      /* copy                 */
++      NULL,                   /* data                 */
++      "IPv6 over IPv6"        /* name                 */
++};
++
++/**
++ * ip6_tunnel_init - register protocol and reserve needed resources
++ *
++ * Return: 0 on success
++ **/
++
++int __init ip6_tunnel_init(void)
++{
++      int i, err;
++
++      ip6ip6_fb_tnl_dev.priv = (void *) &ip6ip6_fb_tnl;
++
++      for (i = 0; i < IP6_TNL_MAXHOOKS; i++) {
++              INIT_LIST_HEAD(&hooks[i]);
++      }
++      if ((err = register_netdev(&ip6ip6_fb_tnl_dev)))
++              return err;
++
++      inet6_add_protocol(&ip6ip6_protocol);
++      return 0;
++}
++
++/**
++ * ip6_tunnel_cleanup - free resources and unregister protocol
++ **/
++
++void ip6_tunnel_cleanup(void)
++{
++      write_lock_bh(&ip6ip6_kernel_lock);
++      shutdown = 1;
++      write_unlock_bh(&ip6ip6_kernel_lock);
++      flush_scheduled_tasks();
++      manage_kernel_tnls(NULL);
++      inet6_del_protocol(&ip6ip6_protocol);
++      unregister_netdev(&ip6ip6_fb_tnl_dev);
++}
++
++#ifdef MODULE
++module_init(ip6_tunnel_init);
++module_exit(ip6_tunnel_cleanup);
++#endif
++
++#if defined(CONFIG_IPV6_MOBILITY_HA_MODULE) || defined(CONFIG_IPV6_MOBILITY_MN_MODULE)
++EXPORT_SYMBOL(ip6ip6_tnl_register_hook);
++EXPORT_SYMBOL(ip6ip6_tnl_unregister_hook);
++#endif
++#ifdef CONFIG_IPV6_MOBILITY_HA_MODULE
++EXPORT_SYMBOL(ip6ip6_tnl_dec_max_kdev_count);
++EXPORT_SYMBOL(ip6ip6_tnl_inc_max_kdev_count);
++EXPORT_SYMBOL(ip6ip6_tnl_dec_min_kdev_count);
++EXPORT_SYMBOL(ip6ip6_tnl_inc_min_kdev_count);
++EXPORT_SYMBOL(ip6ip6_kernel_tnl_add);
++EXPORT_SYMBOL(ip6ip6_kernel_tnl_del);
++EXPORT_SYMBOL(ip6ip6_tnl_lookup);
++#endif
++#ifdef CONFIG_IPV6_MOBILITY_MN_MODULE
++EXPORT_SYMBOL(ip6ip6_tnl_create);
++EXPORT_SYMBOL(ip6ip6_tnl_change);
++#endif
++
+diff -uprN linux-2.4.25.old/net/ipv6/mipglue.c linux-2.4.25/net/ipv6/mipglue.c
+--- linux-2.4.25.old/net/ipv6/mipglue.c        1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mipglue.c    2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,63 @@
++/*
++ *    Glue for Mobility support integration to IPv6
++ *
++ *    Authors:
++ *    Antti Tuominen          <ajtuomin@cc.hut.fi>    
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ *
++ */
++
++#include <linux/sched.h>
++
++#include <net/ipv6.h>
++#include <net/addrconf.h>
++#include <net/neighbour.h>
++#include <net/mipglue.h>
++
++extern int ip6_tlvopt_unknown(struct sk_buff *skb, int optoff);
++
++/*  Initialize all zero  */
++struct mipv6_callable_functions mipv6_functions = { NULL };
++
++/* Sets mipv6_functions struct to zero to invalidate all successive
++ * calls to mipv6 functions. Used on module unload. */
++
++void mipv6_invalidate_calls(void)
++{
++      memset(&mipv6_functions, 0, sizeof(mipv6_functions));
++}
++
++
++/* Selects correct handler for tlv encoded destination option. Called
++ * by ip6_parse_tlv. Checks if mipv6 calls are valid before calling. */
++
++int mipv6_handle_dstopt(struct sk_buff *skb, int optoff)
++{
++      int ret;
++
++        switch (skb->nh.raw[optoff]) {
++      case MIPV6_TLV_HOMEADDR: 
++              ret = MIPV6_CALLFUNC(mipv6_handle_homeaddr, 0)(skb, optoff);
++              break;
++      default:
++              /* Should never happen */
++              printk(KERN_ERR __FILE__ ": Invalid destination option code (%d)\n",
++                     skb->nh.raw[optoff]);
++              ret = 1;
++              break;
++      }
++
++      /* If mipv6 handlers are not valid, pass the packet to
++         * ip6_tlvopt_unknown() for correct handling. */
++      if (!ret)
++              return ip6_tlvopt_unknown(skb, optoff);
++
++      return ret;
++}
++
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/Config.in linux-2.4.25/net/ipv6/mobile_ip6/Config.in
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/Config.in     1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/Config.in 2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,12 @@
++#
++# Mobile IPv6 Configuration
++#
++dep_tristate '    IPv6: Mobility Support (Correspondent Node)' CONFIG_IPV6_MOBILITY $CONFIG_IPV6
++if [ "$CONFIG_IPV6_IPV6_TUNNEL" != "n" ]; then
++   dep_tristate '      MIPv6: Mobile Node Support' CONFIG_IPV6_MOBILITY_MN $CONFIG_IPV6_MOBILITY
++
++   dep_tristate '      MIPv6: Home Agent Support' CONFIG_IPV6_MOBILITY_HA $CONFIG_IPV6_MOBILITY
++fi
++if [ "$CONFIG_IPV6_MOBILITY" != "n" ]; then
++   bool '      MIPv6: Debug messages' CONFIG_IPV6_MOBILITY_DEBUG
++fi
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/Makefile linux-2.4.25/net/ipv6/mobile_ip6/Makefile
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/Makefile      1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/Makefile  2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,35 @@
++#
++# Makefile for the MIPL Mobile IPv6 for Linux.
++#
++# Note! Dependencies are done automagically by 'make dep', which also
++# removes any old dependencies. DON'T put your own dependencies here
++# unless it's something special (ie not a .c file).
++#
++
++
++O_TARGET := mip6_base.o
++
++list-multi := mip6_ha.o mip6_mn.o
++
++obj-y := hashlist.o bcache.o mobhdr_common.o stats.o exthdrs.o \
++      rr_crypto.o hmac.o auth_opt.o mipv6_icmp.o module_cn.o
++
++obj-m := $(O_TARGET)
++
++mip6_ha-objs := halist.o mipv6_icmp_ha.o tunnel_ha.o \
++              ndisc_ha.o ha.o module_ha.o
++
++mip6_mn-objs := mipv6_icmp_mn.o ioctl_mn.o tunnel_mn.o \
++              mdetect.o bul.o multiaccess_ctl.o mobhdr_mn.o mn.o \
++              module_mn.o 
++
++obj-$(CONFIG_IPV6_MOBILITY_HA) += mip6_ha.o
++obj-$(CONFIG_IPV6_MOBILITY_MN) += mip6_mn.o
++
++include $(TOPDIR)/Rules.make
++
++mip6_ha.o: $(mip6_ha-objs)
++      $(LD) -r -o $@ $(mip6_ha-objs)
++
++mip6_mn.o: $(mip6_mn-objs)
++      $(LD) -r -o $@ $(mip6_mn-objs)
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/README linux-2.4.25/net/ipv6/mobile_ip6/README
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/README        1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/README    2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,15 @@
++MIPL Mobile IPv6 for Linux
++
++More information at http://www.mipl.mediapoli.com/.
++
++To join MIPL Mobile IPv6 for Linux mailing lists go to:
++
++      http://www.mipl.mediapoli.com/cgi-bin/mailman/listinfo
++
++Or send mail with subject "subscribe" for the general list to:
++
++      mipl-request@list.mipl.mediapoli.com
++
++or for the developer list to:
++
++      mipl-devel-request@list.mail.mediapoli.com
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/auth_opt.c linux-2.4.25/net/ipv6/mobile_ip6/auth_opt.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/auth_opt.c    1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/auth_opt.c        2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,121 @@
++/*
++ *    MIPv6 Binding Authentication Data Option functions
++ *    
++ *      Authors: 
++ *      Henrik Petander         <lpetande@tml.hut.fi>
++ * 
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#include <linux/autoconf.h>
++#include <linux/icmpv6.h>
++#include <net/mipv6.h>
++
++#include "debug.h"
++#include "hmac.h"
++#include "mobhdr.h"
++
++#define DBG_KEY 5
++
++int mipv6_auth_build(struct in6_addr *cn_addr, struct in6_addr *coa, 
++                   __u8 *mh, __u8 *aud_data, __u8 *k_bu)
++{
++      /* First look up the peer from sadb based on his address */ 
++      struct ah_processing ahp;
++
++      /* Don't add any other options or this system is screwed */
++
++      __u8 buf[MAX_HASH_LENGTH];  
++      
++      
++      if (!k_bu) {
++              DEBUG(DBG_ERROR, "k_bu missing, aborting");
++              return -1;
++      }
++      DEBUG(DBG_KEY, "Key for building authenticator:");
++      debug_print_buffer(DBG_KEY, k_bu, HMAC_SHA1_KEY_SIZE);
++
++      if (ah_hmac_sha1_init(&ahp, k_bu,  HMAC_SHA1_KEY_SIZE) < 0) {
++              DEBUG(DBG_ERROR, "Failed to initialize hmac sha1");
++                return -1; 
++        } 
++
++      DEBUG(DBG_KEY, "coa: ");
++      debug_print_buffer(DBG_KEY, coa, 16);
++      DEBUG(DBG_KEY, "cn_addr: ");
++      debug_print_buffer(DBG_KEY, cn_addr, 16);
++      DEBUG(DBG_KEY, "MH contents: ");
++      debug_print_buffer(DBG_KEY, mh, aud_data - mh);
++
++      /* First the common part */
++      ah_hmac_sha1_loop(&ahp, coa, sizeof(struct in6_addr));
++      ah_hmac_sha1_loop(&ahp, cn_addr, sizeof(struct in6_addr));
++      ah_hmac_sha1_loop(&ahp, mh, aud_data - mh);
++      ah_hmac_sha1_result(&ahp, buf);
++
++      memcpy(aud_data, buf,  MIPV6_RR_MAC_LENGTH);
++
++      return 0;
++}
++
++int mipv6_auth_check(struct in6_addr *cn_addr, struct in6_addr *coa,
++                   __u8 *opt, __u8 optlen, 
++                   struct mipv6_mo_bauth_data *aud, __u8 *k_bu)
++{
++      int ret = -1;
++      struct ah_processing ahp;
++      __u8 htarget[MAX_HASH_LENGTH];
++
++      /* Look up peer by home address */ 
++      if (!k_bu) {
++              DEBUG(DBG_ERROR, "k_bu missing, aborting"); 
++              return -1;
++      }
++
++      DEBUG(DBG_KEY, "Key for checking authenticator:");
++      debug_print_buffer(DBG_KEY, k_bu, HMAC_SHA1_KEY_SIZE);
++
++      if (!aud || !coa) {
++              DEBUG(DBG_INFO, "%s is NULL", aud ? "coa" : "aud");
++              goto out;
++      }
++
++      if (aud->length != MIPV6_RR_MAC_LENGTH) {
++              DEBUG(DBG_ERROR,
++                       ": Incorrect authentication option length %d", aud->length); 
++              goto out; 
++      }
++      
++      if (ah_hmac_sha1_init(&ahp, k_bu, HMAC_SHA1_KEY_SIZE) < 0) { 
++                DEBUG(DBG_ERROR,
++                       "internal error in initialization of authentication algorithm");
++              goto out;
++        } 
++      DEBUG(DBG_KEY, "coa: ");
++      debug_print_buffer(DBG_KEY, coa, 16);
++      DEBUG(DBG_KEY, "cn_addr: ");
++      debug_print_buffer(DBG_KEY, cn_addr, 16);
++      DEBUG(DBG_KEY, "MH contents: ");
++      debug_print_buffer(DBG_KEY, opt, (u8*) aud->data - opt);
++
++      ah_hmac_sha1_loop(&ahp, coa, sizeof(struct in6_addr));
++      ah_hmac_sha1_loop(&ahp, cn_addr, sizeof(struct in6_addr));
++
++      /* 
++       * Process MH + options till the start of the authenticator in
++       * Auth. data option
++       */
++      ah_hmac_sha1_loop(&ahp, opt,  (u8 *)aud->data - opt);
++      ah_hmac_sha1_result(&ahp, htarget);
++      if (memcmp(htarget, aud->data, MIPV6_RR_MAC_LENGTH) == 0)
++              ret = 0;
++
++      DEBUG(DBG_ERROR, "returning %d", ret);
++out:  
++      return ret;
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/bcache.c linux-2.4.25/net/ipv6/mobile_ip6/bcache.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/bcache.c      1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/bcache.c  2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,746 @@
++/*
++ *      Binding Cache
++ *
++ *      Authors:
++ *      Juha Mynttinen            <jmynttin@cc.hut.fi>
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++/*
++ *    Changes:
++ *
++ *    Nanno Langstraat        :       Timer code cleaned up, active socket
++ *                                    test rewritten
++ */
++
++#include <linux/autoconf.h>
++#include <linux/sched.h>
++#include <linux/timer.h>
++#include <linux/in6.h>
++#include <linux/init.h>
++#include <linux/spinlock.h>
++#include <linux/proc_fs.h>
++#include <linux/ipv6_route.h>
++#include <net/ipv6.h>
++#include <net/addrconf.h>
++#include <net/tcp.h>
++#include <net/udp.h>
++#include <net/ip6_route.h>
++#include <net/mipv6.h>
++
++#include "bcache.h"
++#include "hashlist.h"
++#include "debug.h"
++#include "mobhdr.h"
++#include "tunnel.h"
++#include "config.h"
++
++#define TIMERDELAY HZ/10
++
++struct mipv6_bcache {
++      struct hashlist *entries;
++      __u32 size;
++      struct timer_list callback_timer;
++};
++
++struct in6_addr_pair {
++      struct in6_addr *a1;
++      struct in6_addr *a2;
++};
++
++static rwlock_t bcache_lock = RW_LOCK_UNLOCKED;
++
++static struct mipv6_bcache bcache;
++
++static int bcache_proc_info(char *buffer, char **start, off_t offset,
++                          int length);
++
++#define MIPV6_BCACHE_HASHSIZE  32
++
++/* Moment of transmission of a BR, in seconds before bcache entry expiry */
++#define BCACHE_BR_SEND_LEAD  3
++
++#define MIPV6_MAX_BRR 3 /* Send 3 BRRs before deleting BC entry */
++#define MIPV6_BRR_RATE HZ /* Send BRRs once per second */
++
++/* 
++ * Internal functions.
++ */
++
++struct cache_entry_iterator_args {
++      struct mipv6_bce **entry;
++};
++
++static int find_first_cache_entry_iterator(void *data, void *args,
++                                         unsigned long *lifetime)
++{
++      struct mipv6_bce *entry =
++          (struct mipv6_bce *) data;
++      struct cache_entry_iterator_args *state =
++          (struct cache_entry_iterator_args *) args;
++
++      ASSERT(entry != NULL);
++
++      if (entry->type == CACHE_ENTRY) {
++              *(state->entry) = entry;
++              return ITERATOR_STOP;   /* stop iteration */
++      } else {
++              return ITERATOR_CONT;   /* continue iteration */
++      }
++}
++
++
++/* 
++ * Get memory for a new bcache entry.  If bcache is full, a cache
++ * entry may be deleted to get space for a home registration, but not
++ * vice versa.
++ */
++static struct mipv6_bce *mipv6_bce_alloc(__u8 type)
++{
++      struct mipv6_bce *entry;
++      struct cache_entry_iterator_args args;
++
++      DEBUG_FUNC();
++
++      entry = (struct mipv6_bce *)
++              hashlist_alloc(bcache.entries, SLAB_ATOMIC);
++
++      /* Cache replacement policy: always replace the CACHE_ENTRY
++           closest to expiration.  Type HOME_REGISTRATION entry may
++           never be deleted before expiration. */
++      if (entry == NULL) {
++              /* cache full, try to delete a CACHE_ENTRY */
++              args.entry = &entry;
++              hashlist_iterate(bcache.entries, &args,
++                               find_first_cache_entry_iterator);
++              if (entry == NULL)
++                      return NULL;
++              hashlist_delete(bcache.entries,
++                              (struct hashlist_entry *)entry);
++              entry = (struct mipv6_bce *)
++                      hashlist_alloc(bcache.entries, SLAB_ATOMIC);
++      }
++      return entry;
++}
++
++/*
++ * Frees entry's memory allocated with mipv6_bce_alloc
++ */
++static void mipv6_bce_free(struct mipv6_bce *entry)
++{
++      hashlist_free(bcache.entries, (void *) entry);
++}
++
++/*
++ * Removes all expired entries 
++ */
++static void expire(void)
++{
++      struct mipv6_bce *entry;
++      struct br_addrs {
++              struct in6_addr daddr;
++              struct in6_addr saddr;
++              struct br_addrs *next;
++      };
++      struct br_addrs *br_info = NULL;
++
++      DEBUG_FUNC();
++
++      write_lock(&bcache_lock);
++
++      while ((entry = (struct mipv6_bce *)
++              hashlist_get_first(bcache.entries)) != NULL) {
++              struct rt6_info *rt;
++              if (time_after_eq(jiffies, entry->callback_time)) {
++
++                      DEBUG(DBG_INFO, "an entry expired");
++
++                      if (entry->type & HOME_REGISTRATION) {
++                              mip6_fn.proxy_del(&entry->home_addr, entry);
++                      }
++                      hashlist_delete(bcache.entries, (void *)entry);
++                      mipv6_bce_free(entry);
++                      entry = NULL;
++              } else if (entry->br_callback_time != 0 &&
++                         time_after_eq(jiffies, entry->br_callback_time) &&
++                         entry->br_count < MIPV6_MAX_BRR &&
++                         (rt = rt6_lookup(&entry->home_addr, &entry->our_addr, 0, 0)) != NULL){
++                      /* Do we have a destination cache entry for the home address */
++                      if (rt->rt6i_flags & RTF_CACHE) {
++                              struct br_addrs *tmp;
++                              tmp = br_info;
++                              DEBUG(DBG_INFO,
++                                    "bcache entry recently used. Sending BR.");
++                              /* queue for sending */
++                              br_info = kmalloc(sizeof(struct br_addrs),
++                                                GFP_ATOMIC);
++                              if (br_info) {
++                                      ipv6_addr_copy(&br_info->saddr,
++                                                     &entry->our_addr);
++                                      ipv6_addr_copy(&br_info->daddr,
++                                                     &entry->home_addr);
++                                      br_info->next = tmp;
++                                      entry->last_br = jiffies;
++                                      entry->br_callback_time = jiffies + MIPV6_BRR_RATE;
++                                      entry->br_count++;
++                              } else {
++                                      br_info = tmp;
++                                      DEBUG(DBG_ERROR, "Out of memory");
++                              }
++                              
++                      } else
++                              entry->br_callback_time = 0;    
++                      dst_release(&rt->u.dst);
++              } else {
++                      entry->br_callback_time = 0;
++                      break;
++              }
++      }
++      write_unlock(&bcache_lock);
++
++      while (br_info) {
++              struct br_addrs *tmp = br_info->next;
++              if (mipv6_send_brr(&br_info->saddr, &br_info->daddr, NULL) < 0)
++                      DEBUG(DBG_WARNING,
++                            "BR send for %x:%x:%x:%x:%x:%x:%x:%x failed",
++                            NIPV6ADDR(&br_info->daddr));
++              kfree(br_info);
++              br_info = tmp;
++      }
++}
++
++static void set_timer(void)
++{
++      struct mipv6_bce *entry;
++      unsigned long callback_time;
++
++      DEBUG_FUNC();
++
++      entry = (struct mipv6_bce *)
++              hashlist_get_first(bcache.entries);
++      if (entry != NULL) {
++              if (entry->br_callback_time > 0 && 
++                  time_after(entry->br_callback_time, jiffies))
++                      callback_time = entry->br_callback_time;
++              else if (time_after(entry->callback_time, jiffies))
++                      callback_time = entry->callback_time;
++              else {
++                      DEBUG(DBG_WARNING, 
++                            "bcache timer attempted to schedule"
++                            " for a historical jiffies count!");
++                      callback_time = jiffies + TIMERDELAY;
++              }
++              
++              DEBUG(DBG_INFO, "setting timer to now");
++              mod_timer(&bcache.callback_timer, callback_time);
++      } else {
++              del_timer(&bcache.callback_timer);
++              DEBUG(DBG_INFO, "BC empty, not setting a new timer");
++      }
++}
++
++/* 
++ * The function that is scheduled to do the callback functions. May be
++ * modified e.g to allow Binding Requests, now only calls expire() and
++ * schedules a new timer.
++ */
++static void timer_handler(unsigned long dummy)
++{
++      expire();
++      write_lock(&bcache_lock);
++      set_timer();
++      write_unlock(&bcache_lock);
++}
++
++/*
++ * Interface functions visible to other modules
++ */
++
++/**
++ * mipv6_bcache_add - add Binding Cache entry
++ * @ifindex: interface index
++ * @our_addr: own address
++ * @home_addr_org: MN's home address
++ * @coa: MN's care-of address
++ * @lifetime: lifetime for this binding
++ * @prefix: prefix length
++ * @seq: sequence number
++ * @flags: flags received in BU
++ * @type: type of entry
++ *
++ * Adds an entry for this @home_addr_org in the Binding Cache.  If entry
++ * already exists, old entry is updated.  @type may be %CACHE_ENTRY or
++ * %HOME_REGISTRATION.
++ **/
++int mipv6_bcache_add(int ifindex,
++                   struct in6_addr *our_addr,
++                   struct in6_addr *home_addr,
++                   struct in6_addr *coa,
++                   __u32 lifetime, __u16 seq, __u8 flags, __u8 type)
++{
++      struct mipv6_bce *entry;
++      int update = 0;
++      int create_tunnel = 0;
++      unsigned long now = jiffies;
++      struct in6_addr_pair hashkey;
++      int ret = -1;
++
++      DEBUG_FUNC();
++
++      hashkey.a1 = home_addr;
++      hashkey.a2 = our_addr;
++
++      write_lock(&bcache_lock);
++
++      if (type == HOME_REGISTRATION && !(mip6node_cnf.capabilities&CAP_HA))
++              return 0;
++
++      if (unlikely(bcache.entries == NULL)) {
++              ret = -ENOMEM;
++              goto err;
++      }
++
++      if ((entry = (struct mipv6_bce *)
++           hashlist_get(bcache.entries, &hashkey)) != NULL) {
++              /* if an entry for this home_addr exists (with smaller
++               * seq than the new seq), update it by removing it
++               * first
++               */
++              if (!MIPV6_SEQ_GT(seq, entry->seq)) {
++                      DEBUG(DBG_INFO, "smaller seq than existing, not updating");
++                      goto out;
++              }
++              DEBUG(DBG_INFO, "updating an existing entry");
++              update = 1;
++
++              /* H-flag is already checked in BU handler. */
++              /* XXX: Should we care about the other flags?*/
++              if (flags != entry->flags) {
++                      DEBUG(DBG_INFO, "entry/BU flag mismatch");
++              }
++
++              if (type == HOME_REGISTRATION) {
++                      create_tunnel = (ipv6_addr_cmp(&entry->coa, coa) ||
++                                       entry->ifindex != ifindex);
++              }
++      } else {
++              /* no entry for this home_addr, try to create a new entry */
++              DEBUG(DBG_INFO, "creating a new entry");
++              update = 0;
++
++              entry = mipv6_bce_alloc(type);
++              if (entry == NULL) {
++                      DEBUG(DBG_INFO, "cache full, entry not added");
++                      goto err;
++              }
++
++              create_tunnel = (type == HOME_REGISTRATION);
++      }
++
++      if (create_tunnel) {
++              if (update)
++                      mip6_fn.proxy_del(&entry->home_addr, entry);
++              if (mip6_fn.proxy_create(flags, ifindex, coa, our_addr, home_addr) < 0) {
++                      goto err_proxy;
++              }
++      }
++
++      ipv6_addr_copy(&(entry->our_addr), our_addr);
++      ipv6_addr_copy(&(entry->home_addr), home_addr);
++      ipv6_addr_copy(&(entry->coa), coa);
++      entry->ifindex = ifindex;
++      entry->seq = seq;
++      entry->type = type;
++      entry->flags = flags;
++      
++      entry->last_br = 0;
++      entry->destunr_count = 0;
++      entry->callback_time = now + lifetime * HZ;
++      if (entry->type & HOME_REGISTRATION)
++              entry->br_callback_time = 0;
++      else
++              entry->br_callback_time = now +
++                      (lifetime - BCACHE_BR_SEND_LEAD) * HZ;
++      
++      if (update) {
++              DEBUG(DBG_INFO, "updating entry : %x", entry);
++              hashlist_reposition(bcache.entries, (void *)entry, 
++                                  entry->callback_time);
++      } else {
++              DEBUG(DBG_INFO, "adding entry: %x", entry);
++              if ((hashlist_add(bcache.entries,
++                                &hashkey,
++                                entry->callback_time, entry)) < 0) {
++                      
++                      DEBUG(DBG_ERROR, "Hash add failed");
++                      goto err_hashlist;
++              }
++      }
++      
++      set_timer();
++      
++out:
++      write_unlock(&bcache_lock);
++      return 0;
++
++err_hashlist:
++      if (create_tunnel) {
++              mip6_fn.proxy_del(home_addr, entry);
++      }
++err_proxy:
++      if (update) {
++              hashlist_delete(bcache.entries, (void *)entry);
++      }
++      mipv6_bce_free(entry);
++err:
++      write_unlock(&bcache_lock);
++      return ret;
++}
++
++int mipv6_bcache_icmp_err(struct in6_addr *home_addr,
++                        struct in6_addr *our_addr, 
++                        int destunr_count)
++{
++      struct mipv6_bce *entry;
++      struct in6_addr_pair hashkey;
++
++      int ret = -ENOENT;
++
++      DEBUG_FUNC();
++
++      hashkey.a1 = home_addr;
++      hashkey.a2 = our_addr;
++
++      write_lock(&bcache_lock);
++      if (unlikely(bcache.entries == NULL)) {
++              ret = -ENOMEM;
++              goto err;
++      }
++
++      if ((entry = (struct mipv6_bce *)
++           hashlist_get(bcache.entries, &hashkey)) != NULL) {
++              entry->last_destunr = jiffies;
++              entry->destunr_count = destunr_count;
++              ret = 0;
++      }
++err:
++      write_unlock(&bcache_lock);
++      return ret;
++}
++
++
++/**
++ * mipv6_bcache_delete - delete Binding Cache entry
++ * @home_addr: MN's home address
++ * @our_addr: our address
++ * @type: type of entry
++ *
++ * Deletes an entry associated with @home_addr from Binding Cache.
++ * Valid values for @type are %CACHE_ENTRY, %HOME_REGISTRATION and
++ * %ANY_ENTRY.  %ANY_ENTRY deletes any type of entry.
++ **/
++int mipv6_bcache_delete(struct in6_addr *home_addr,
++                      struct in6_addr *our_addr, __u8 type)
++{
++      struct mipv6_bce *entry;
++      struct in6_addr_pair hashkey;
++      int err = 0;
++
++      DEBUG_FUNC();
++
++      if (home_addr == NULL || our_addr == NULL) {
++              DEBUG(DBG_INFO, "error in arguments");
++              return -EINVAL;
++      }
++
++      hashkey.a1 = home_addr;
++      hashkey.a2 = our_addr;
++
++      write_lock(&bcache_lock);
++
++      if (unlikely(bcache.entries == NULL) ||
++          (entry = (struct mipv6_bce *)
++           hashlist_get(bcache.entries, &hashkey)) == NULL ||
++          !(entry->type & type)) {
++              DEBUG(DBG_INFO, "No matching entry found");
++              err = -ENOENT;
++              goto out;
++      }
++
++      hashlist_delete(bcache.entries, (void *) entry);
++      mipv6_bce_free(entry);
++
++      set_timer();
++out:
++      write_unlock(&bcache_lock);
++      return err;
++}
++
++/**
++ * mipv6_bcache_exists - check if entry exists
++ * @home_addr: home address to check
++ * @our_addr: our address
++ *
++ * Determines if a binding exists for @home_addr.  Returns type of the
++ * entry or negative if entry does not exist.
++ **/
++int mipv6_bcache_exists(struct in6_addr *home_addr,
++                      struct in6_addr *our_addr)
++{
++      struct mipv6_bce *entry;
++      struct in6_addr_pair hashkey;
++      int type = -ENOENT;
++
++      DEBUG_FUNC();
++
++      if (home_addr == NULL || our_addr == NULL)
++              return -EINVAL;
++
++      hashkey.a1 = home_addr;
++      hashkey.a2 = our_addr;
++
++      read_lock(&bcache_lock);
++      if (likely(bcache.entries != NULL) &&
++          (entry = (struct mipv6_bce *)
++           hashlist_get(bcache.entries, &hashkey)) != NULL) {
++              type = entry->type;
++      }
++      read_unlock(&bcache_lock);
++
++      return type;
++}
++
++/**
++ * mipv6_bcache_get - get entry from Binding Cache
++ * @home_addr: home address to search
++ * @our_addr: our address
++ * @entry: pointer to buffer
++ *
++ * Gets a copy of Binding Cache entry for @home_addr. If entry 
++ * exists entry is copied to @entry and zero is returned.  
++ * Otherwise returns negative.
++ **/
++int mipv6_bcache_get(struct in6_addr *home_addr,
++                   struct in6_addr *our_addr,
++                   struct mipv6_bce *entry)
++{
++      struct mipv6_bce *entry2;
++      struct in6_addr_pair hashkey;
++      int ret = -ENOENT;
++
++      DEBUG_FUNC();
++
++      if (home_addr == NULL || our_addr == NULL || entry == NULL)
++              return -EINVAL;
++
++      hashkey.a1 = home_addr;
++      hashkey.a2 = our_addr;
++
++      read_lock_bh(&bcache_lock);
++
++      entry2 = (struct mipv6_bce *)
++              hashlist_get(bcache.entries, &hashkey);
++      if (entry2 != NULL) {
++              memcpy(entry, entry2, sizeof(struct mipv6_bce));
++              ret = 0;
++      }
++      read_unlock_bh(&bcache_lock);
++      return ret;
++}
++
++int mipv6_bcache_iterate(hashlist_iterator_t func, void *args)
++{
++      int ret;
++
++      read_lock_bh(&bcache_lock);
++      ret = hashlist_iterate(bcache.entries, args, func);
++      read_unlock_bh(&bcache_lock);
++
++      return ret;
++}
++
++/*
++ * Proc-filesystem functions
++ */
++
++#define BC_INFO_LEN 80
++
++struct procinfo_iterator_args {
++      char *buffer;
++      int offset;
++      int length;
++      int skip;
++      int len;
++};
++
++static int procinfo_iterator(void *data, void *args, unsigned long *pref)
++{
++      struct procinfo_iterator_args *arg =
++          (struct procinfo_iterator_args *) args;
++      struct mipv6_bce *entry =
++          (struct mipv6_bce *) data;
++
++      ASSERT(entry != NULL);
++
++      if (arg->skip < arg->offset / BC_INFO_LEN) {
++              arg->skip++;
++              return ITERATOR_CONT;
++      }
++
++      if (arg->len >= arg->length)
++              return ITERATOR_CONT;
++
++      /* HoA CoA CallbackInSecs Type */
++      arg->len += sprintf(arg->buffer + arg->len,
++                          "%08x%08x%08x%08x %08x%08x%08x%08x %010lu %02d\n",
++                          ntohl(entry->home_addr.s6_addr32[0]),
++                          ntohl(entry->home_addr.s6_addr32[1]),
++                          ntohl(entry->home_addr.s6_addr32[2]),
++                          ntohl(entry->home_addr.s6_addr32[3]),
++                          ntohl(entry->coa.s6_addr32[0]),
++                          ntohl(entry->coa.s6_addr32[1]),
++                          ntohl(entry->coa.s6_addr32[2]),
++                          ntohl(entry->coa.s6_addr32[3]),
++                          ((entry->callback_time) - jiffies) / HZ,
++                          (int) entry->type);
++
++      return ITERATOR_CONT;
++}
++
++ /*
++  * Callback function for proc filesystem.
++  */
++static int bcache_proc_info(char *buffer, char **start, off_t offset,
++                          int length)
++{
++      struct procinfo_iterator_args args;
++
++      DEBUG_FUNC();
++
++      args.buffer = buffer;
++      args.offset = offset;
++      args.length = length;
++      args.skip = 0;
++      args.len = 0;
++
++      read_lock_bh(&bcache_lock);
++      hashlist_iterate(bcache.entries, &args, procinfo_iterator);
++      read_unlock_bh(&bcache_lock);
++
++      *start = buffer;
++      if (offset)
++              *start += offset % BC_INFO_LEN;
++
++      args.len -= offset % BC_INFO_LEN;
++
++      if (args.len > length)
++              args.len = length;
++      if (args.len < 0)
++              args.len = 0;
++
++      return args.len;
++}
++
++static int bcache_compare(void *data, void *hashkey)
++{
++      struct in6_addr_pair *p = (struct in6_addr_pair *) hashkey;
++      struct mipv6_bce *e = (struct mipv6_bce *) data;
++
++      if (ipv6_addr_cmp(&e->home_addr, p->a1) == 0
++          && ipv6_addr_cmp(&e->our_addr, p->a2) == 0)
++              return 0;
++      else
++              return -1;
++}
++
++static __u32 bcache_hash(void *hashkey)
++{
++      struct in6_addr_pair *p = (struct in6_addr_pair *) hashkey;
++
++      return p->a1->s6_addr32[0] ^ p->a1->s6_addr32[1] ^
++              p->a2->s6_addr32[2] ^ p->a2->s6_addr32[3];
++}
++
++/* 
++ * Initialization and shutdown functions
++ */
++
++int __init mipv6_bcache_init(__u32 size)
++{
++      if (size < 1) {
++              DEBUG(DBG_ERROR, "Binding cache size must be at least 1");
++              return -EINVAL;
++      }
++      bcache.entries = hashlist_create(MIPV6_BCACHE_HASHSIZE, size,
++                                       sizeof(struct mipv6_bce),
++                                       "mip6_bcache", NULL, NULL,
++                                       bcache_compare, bcache_hash);
++
++      if (bcache.entries == NULL) {
++              DEBUG(DBG_ERROR, "Failed to initialize hashlist");
++              return -ENOMEM;
++      }
++
++      init_timer(&bcache.callback_timer);
++      bcache.callback_timer.data = 0;
++      bcache.callback_timer.function = timer_handler;
++      bcache.size = size;
++
++      proc_net_create("mip6_bcache", 0, bcache_proc_info);
++
++      DEBUG(DBG_INFO, "Binding cache initialized");
++      return 0;
++}
++
++static int 
++bce_cleanup_iterator(void *rawentry, void *args, unsigned long *sortkey)
++{
++      int type = (int) args;
++      struct mipv6_bce *entry = (struct mipv6_bce *) rawentry;
++      if (entry->type == type) {
++              if (entry->type & HOME_REGISTRATION) {
++                      if (unlikely(mip6_fn.proxy_del == NULL))
++                              DEBUG(DBG_ERROR, "proxy_del unitialized");
++                      else
++                              mip6_fn.proxy_del(&entry->home_addr, entry);
++              }
++              return ITERATOR_DELETE_ENTRY;
++      }
++      return ITERATOR_CONT;
++
++}
++
++void mipv6_bcache_cleanup(int type)
++{
++      write_lock_bh(&bcache_lock);
++      hashlist_iterate(bcache.entries,(void *) type, bce_cleanup_iterator);
++      write_unlock_bh(&bcache_lock);
++}
++
++int __exit mipv6_bcache_exit(void)
++{
++      struct hashlist *entries;
++
++      DEBUG_FUNC();
++
++      proc_net_remove("mip6_bcache");
++
++      write_lock_bh(&bcache_lock);
++      DEBUG(DBG_INFO, "Stopping the bcache timer");
++      del_timer(&bcache.callback_timer);
++      hashlist_iterate(bcache.entries,(void *)CACHE_ENTRY, 
++                       bce_cleanup_iterator);
++
++      entries = bcache.entries;
++      bcache.entries = NULL;
++      write_unlock_bh(&bcache_lock);
++
++      hashlist_destroy(entries);
++      return 0;
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/bcache.h linux-2.4.25/net/ipv6/mobile_ip6/bcache.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/bcache.h      1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/bcache.h  2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,72 @@
++/*
++ *      MIPL Mobile IPv6 Binding Cache header file
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _BCACHE_H
++#define _BCACHE_H
++
++#include <linux/in6.h>
++#include <linux/timer.h>
++#include "hashlist.h"
++
++#define CACHE_ENTRY 1 /* this and HOME_REGISTRATION are the entry types */
++#define HOME_REGISTRATION 2
++#define ANY_ENTRY 3
++
++#define MIPV6_MAX_DESTUNREACH 5 /* Delete CN BCEs after 5 destination unreachables */
++#define MIPV6_DEST_UNR_IVAL  10 /* What is the max interval of destination  
++                                 unreacahable error messages for them to be persistent*/
++
++struct mipv6_bce {
++      struct hashlist_entry e;
++      int ifindex;                            /* Interface identifier */
++      struct in6_addr our_addr;               /* our address (as seen by the MN) */
++      struct in6_addr home_addr;              /* MN home address */
++      struct in6_addr coa;                    /* MN care-of address */
++      unsigned long callback_time;            /* time of expiration     (in jiffies) */
++      unsigned long br_callback_time;         /* time for sending a BR  (in jiffies) */
++      int (*callback_function)(struct mipv6_bce *entry);
++      __u8 type;                              /* home registration */
++      __u8 router;                            /* mn is router */
++      __u8 flags;                             /* flags received in BU */
++      __u16 seq;                              /* sequence number */
++      unsigned long last_br;                  /* time when last BR sent */
++      unsigned long last_destunr;             /* time when last ICMP destination unreachable received */
++      int br_count;                           /* How many BRRs have sent */
++      int destunr_count;                      /* Number of destination unreachables received */   
++};
++
++int mipv6_bcache_add(int ifindex, struct in6_addr *our_addr, 
++                   struct in6_addr *home_addr, struct in6_addr *coa,
++                   __u32 lifetime, __u16 seq, __u8 flags, __u8 type);
++
++int mipv6_bcache_icmp_err(struct in6_addr *home_addr,
++                        struct in6_addr *our_addr, 
++                        int destunr_count);
++
++int mipv6_bcache_delete(struct in6_addr *home_addr, struct in6_addr *our_addr,
++                      __u8 type);
++
++int mipv6_bcache_exists(struct in6_addr *home_addr,
++                      struct in6_addr *our_addr);
++
++int mipv6_bcache_get(struct in6_addr *home_addr,
++                   struct in6_addr *our_addr,
++                   struct mipv6_bce *entry);
++
++int mipv6_bcache_iterate(int (*func)(void *, void *, unsigned long *), void *args);
++
++void mipv6_bcache_cleanup(int type);
++
++int mipv6_bcache_init(__u32 size);
++
++int mipv6_bcache_exit(void);
++
++#endif /* _BCACHE_H */
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/bul.c linux-2.4.25/net/ipv6/mobile_ip6/bul.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/bul.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/bul.c     2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,634 @@
++/*
++ *      Binding update list
++ *
++ *      Authors:
++ *      Juha Mynttinen            <jmynttin@cc.hut.fi>
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++/*
++ *    Changes:
++ *
++ *    Nanno Langstraat        :       Timer code cleaned up
++ */
++
++#include <linux/autoconf.h>
++#include <linux/sched.h>
++#include <linux/timer.h>
++#include <linux/in6.h>
++#include <linux/init.h>
++#include <linux/spinlock.h>
++#include <net/ipv6.h>
++#include <net/mipv6.h>
++#include <linux/proc_fs.h>
++
++#include "bul.h"
++#include "debug.h"
++#include "hashlist.h"
++#include "tunnel_mn.h"
++#include "mobhdr.h"
++
++#define MIPV6_BUL_HASHSIZE 32
++
++rwlock_t bul_lock = RW_LOCK_UNLOCKED;
++
++struct mipv6_bul {
++      struct hashlist *entries;
++      struct timer_list callback_timer;
++};
++
++static struct mipv6_bul bul;
++
++struct in6_addr_pair {
++      struct in6_addr *a1;
++      struct in6_addr *a2;
++};
++
++/**********************************************************************
++ *
++ * Private functions
++ *
++ **********************************************************************/
++
++static int bul_compare(void *data, void *hashkey)
++{
++      struct in6_addr_pair *p = (struct in6_addr_pair *)hashkey;
++      struct mipv6_bul_entry *e = (struct mipv6_bul_entry *)data;
++
++      if (ipv6_addr_cmp(&e->cn_addr, p->a1) == 0
++          && ipv6_addr_cmp(&e->home_addr, p->a2) == 0)
++              return 0;
++      else
++              return -1;
++}
++
++struct test_keys {
++      struct in6_addr *addr;
++      u8 *cookie;
++};
++
++static int bul_compare_cookie(void *data, void *keys)
++{
++      struct test_keys *p = (struct test_keys *)keys;
++      struct mipv6_bul_entry *e = (struct mipv6_bul_entry *)data;
++
++      if (ipv6_addr_cmp(&e->cn_addr, p->addr) == 0 && e->rr
++          && memcmp(&e->rr->cot_cookie, p->cookie, 8) == 0)
++              return 0;
++      else
++              return -1;
++}
++
++static u32 bul_hash(void *hashkey)
++{
++      struct in6_addr_pair *p = (struct in6_addr_pair *)hashkey;
++      
++      return p->a1->s6_addr32[0] ^
++              p->a1->s6_addr32[1] ^
++              p->a1->s6_addr32[2] ^
++              p->a1->s6_addr32[3];
++}
++
++static int bul_proc_info(char *buffer, char **start, off_t offset,
++                          int length);
++
++static struct mipv6_bul_entry *mipv6_bul_get_entry(void)
++{
++      DEBUG_FUNC();
++      return ((struct mipv6_bul_entry *) 
++              hashlist_alloc(bul.entries, SLAB_ATOMIC));
++}
++
++static void mipv6_bul_entry_free(struct mipv6_bul_entry *entry)
++{
++      DEBUG_FUNC();           
++
++      if (entry->rr) {
++              if (entry->rr->kbu)
++                      kfree(entry->rr->kbu);
++              kfree(entry->rr);
++      }
++      if (entry->ops)
++              kfree(entry->ops);
++      hashlist_free(bul.entries, (void *)entry);
++}
++
++static __inline__ int del_bul_entry_tnl(struct mipv6_bul_entry *entry) 
++{
++      if (entry->flags & MIPV6_BU_F_HOME) {
++              return mipv6_mv_tnl_to_ha(&entry->cn_addr, 
++                                          &entry->coa,
++                                          &entry->home_addr);
++      }
++      return 0;
++}
++
++static void timer_update(void)
++{
++      struct mipv6_bul_entry *entry;
++
++      DEBUG_FUNC();
++
++      entry = hashlist_get_first(bul.entries);
++
++      while (entry && time_after_eq(jiffies, entry->callback_time)) {
++              if (time_after_eq(jiffies, entry->expire) ||
++                  entry->callback(entry) != 0) {
++                      /*
++                       * Either the entry has expired, or the callback
++                       * indicated that it should be deleted.
++                       */
++                      hashlist_delete(bul.entries, (void *)entry);
++                      
++                      del_bul_entry_tnl(entry);
++                      mipv6_bul_entry_free(entry);
++                      DEBUG(DBG_INFO, "Entry deleted (was expired) from "
++                            "binding update list");
++              } else {
++                      /* move entry to its right place in the hashlist */
++                      DEBUG(DBG_INFO, "Rescheduling");
++                      hashlist_reposition(bul.entries, (void *)entry,
++                                          entry->callback_time);
++              }
++              entry = (struct mipv6_bul_entry *)
++                      hashlist_get_first(bul.entries);
++      }
++
++      if (entry == NULL) {
++              DEBUG(DBG_INFO, "bul empty, not setting a new timer");
++              del_timer(&bul.callback_timer);
++      } else {
++              mod_timer(&bul.callback_timer, entry->callback_time);
++      }
++}
++
++static void timer_handler(unsigned long dummy)
++{
++      DEBUG_FUNC();
++
++      write_lock(&bul_lock);
++      timer_update();
++      write_unlock(&bul_lock);
++}
++
++/**********************************************************************
++ *
++ * Public interface functions
++ *
++ **********************************************************************/
++
++/**
++ * mipv6_bul_iterate - apply interator function to all entries
++ * @func: function to apply
++ * @args: extra arguments for iterator
++ *
++ * Applies @func for each entry in Binding Update List.  Extra
++ * arguments given in @args are also passed to the iterator function.
++ * Caller must hold @bul_lock.
++ **/
++int mipv6_bul_iterate(hashlist_iterator_t func, void *args)
++{
++      DEBUG_FUNC();
++
++      return hashlist_iterate(bul.entries, args, func);
++}
++
++/**
++ * mipv6_bul_exists - check if Binding Update List entry exists
++ * @cn: address to check
++ *
++ * Checks if Binding Update List has an entry for @cn.  Returns true
++ * if entry exists, false otherwise. Caller may not hold @bul_lock.
++ **/
++int mipv6_bul_exists(struct in6_addr *cn, struct in6_addr *haddr)
++{
++      int exists;
++      struct in6_addr_pair hashkey;
++
++      DEBUG_FUNC();
++
++      hashkey.a1 = cn;
++      hashkey.a2 = haddr;
++      
++      read_lock_bh(&bul_lock);
++
++      if (unlikely(bul.entries == NULL))
++              exists = 0;
++      else
++              exists = (hashlist_get(bul.entries, &hashkey) != NULL);
++
++      read_unlock_bh(&bul_lock);
++      return exists;
++}
++
++/**
++ * mipv6_bul_get - get Binding Update List entry
++ * @cn_addr: CN address to search
++ * @home_addr: home address to search
++ *
++ * Returns Binding Update List entry for @cn_addr if it exists.
++ * Otherwise returns %NULL.  Caller must hold @bul_lock.
++ **/
++struct mipv6_bul_entry *mipv6_bul_get(struct in6_addr *cn_addr, 
++                                    struct in6_addr *home_addr)
++{
++      struct mipv6_bul_entry *entry;
++      struct in6_addr_pair hashkey;
++      
++      DEBUG_FUNC();
++
++      if (unlikely(bul.entries == NULL)) {
++              return NULL;
++      }
++      hashkey.a1 = cn_addr;
++      hashkey.a2 = home_addr;
++
++      entry = (struct mipv6_bul_entry *) 
++              hashlist_get(bul.entries, &hashkey);
++              
++      return entry;
++}
++
++struct mipv6_bul_entry *mipv6_bul_get_by_ccookie(
++      struct in6_addr *cn_addr, u8 *cookie)
++{
++      struct test_keys key;
++
++      DEBUG_FUNC();
++
++      if (unlikely(bul.entries == NULL))
++              return NULL;
++      key.addr = cn_addr;
++      key.cookie = cookie;
++
++      return (struct mipv6_bul_entry *) 
++              hashlist_get_ex(bul.entries, &key,
++                              bul_compare_cookie);
++}
++
++/**
++ * mipv6_bul_reschedule - reschedule Binding Update List entry
++ * @entry: entry to reschedule
++ *
++ * Reschedules a Binding Update List entry.  Must be called after
++ * modifying entry lifetime.  Caller must hold @bul_lock (write).
++ **/
++void mipv6_bul_reschedule(struct mipv6_bul_entry *entry)
++{
++      DEBUG_FUNC();
++
++      hashlist_reposition(bul.entries,
++                          (void *)entry,
++                          entry->callback_time);
++      timer_update();
++}
++
++/**
++ * mipv6_bul_add - add binding update to Binding Update List
++ * @cn_addr: IPv6 address where BU was sent
++ * @home_addr: Home address for this binding
++ * @coa: Care-of address for this binding
++ * @lifetime: expiration time of the binding in seconds
++ * @seq: sequence number of the BU
++ * @flags: %MIPV6_BU_F_* flags
++ * @callback: callback function called on expiration
++ * @callback_time: expiration time for callback
++ * @state: binding send state
++ * @delay: retransmission delay
++ * @maxdelay: retransmission maximum delay
++ * @ops: Mobility header options for BU
++ * @rr: Return routability information
++ *
++ * Adds a binding update sent to @cn_addr for @home_addr to the
++ * Binding Update List.  If entry already exists, it is updated.
++ * Entry is set to expire in @lifetime seconds.  Entry has a callback
++ * function @callback that is called at @callback_time.  Entry @state
++ * controls resending of this binding update and it can be set to
++ * %ACK_OK, %RESEND_EXP or %ACK_ERROR.  Returns a pointer to the newly
++ * created or updated entry.  Caller must hold @bul_lock (write).
++ **/
++struct mipv6_bul_entry *mipv6_bul_add(
++      struct in6_addr *cn_addr, struct in6_addr *home_addr,
++      struct in6_addr *coa, 
++      __u32 lifetime, __u16 seq, __u8 flags,
++      int (*callback)(struct mipv6_bul_entry *entry),
++      __u32 callback_time, 
++      __u8 state, __u32 delay, __u32 maxdelay,
++      struct mipv6_mh_opt *ops, 
++      struct mipv6_rr_info *rr)
++{
++      struct mipv6_bul_entry *entry;
++      int update = 0;
++      struct in6_addr_pair hashkey;
++
++      DEBUG_FUNC();
++
++      if (unlikely(bul.entries == NULL))
++              return NULL;
++
++      if (cn_addr == NULL || home_addr == NULL || coa == NULL || 
++          lifetime < 0 || callback == NULL || callback_time < 0 || 
++          (state != ACK_OK && state != RESEND_EXP && state != ACK_ERROR) ||
++          delay < 0 || maxdelay < 0) {
++              DEBUG(DBG_ERROR, "invalid arguments");
++              return NULL;
++      }
++      DEBUG(DBG_INFO, "cn_addr: %x:%x:%x:%x:%x:%x:%x:%x, "
++            "home_addr: %x:%x:%x:%x:%x:%x:%x:%x"
++            "coaddr: %x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(cn_addr), 
++             NIPV6ADDR(home_addr), NIPV6ADDR(coa));
++      hashkey.a1 = cn_addr;
++      hashkey.a2 = home_addr;
++      
++      /* 
++       * decide whether to add a new entry or update existing, also
++       * check if there's room for a new entry when adding a new
++       * entry (latter is handled by mipv6_bul_get_entry() 
++       */
++      if ((entry = (struct mipv6_bul_entry *)
++           hashlist_get(bul.entries, &hashkey)) != NULL) {
++              /* if an entry for this cn_addr exists (with smaller
++               * seq than the new entry's seq), update it */
++              
++              if (MIPV6_SEQ_GT(seq, entry->seq)) {
++                      DEBUG(DBG_INFO, "updating an existing entry");
++                      update = 1;
++              } else {
++                      DEBUG(DBG_INFO, "smaller seq than existing, not updating");
++                      return NULL;
++              }
++      } else {
++              entry = mipv6_bul_get_entry();
++              if (entry == NULL) {
++                      DEBUG(DBG_WARNING, "binding update list full, can't add!!!");
++                      return NULL;
++              }
++              memset(entry, 0, sizeof(*entry));
++              /* First BU send happens here, save count in the entry */
++              entry->consecutive_sends = 1;
++      }
++
++      if (!update) {
++              ipv6_addr_copy(&(entry->cn_addr), cn_addr);
++              ipv6_addr_copy(&(entry->home_addr), home_addr);
++              entry->ops = ops;
++      }
++      /* Add Return Routability info to bul entry */
++      if (rr) {
++              if(entry->rr)
++                      kfree(entry->rr); 
++              entry->rr = rr;
++      }
++
++      ipv6_addr_copy(&(entry->coa), coa);
++      entry->lifetime = lifetime;
++      if (lifetime)
++              entry->expire = jiffies + lifetime * HZ;
++      else if (flags & MIPV6_BU_F_ACK)
++              entry->expire = jiffies + HOME_RESEND_EXPIRE * HZ;
++      entry->seq = seq;
++      entry->flags = flags;
++      entry->lastsend = jiffies; /* current time = last use of the entry */
++      entry->state = state;
++      entry->delay = delay;
++      entry->maxdelay = maxdelay;
++      entry->callback_time = jiffies + callback_time * HZ;
++      entry->callback = callback;
++
++      if (flags & MIPV6_BU_F_HOME && 
++          mipv6_mv_tnl_to_ha(cn_addr, coa, home_addr)) {
++              DEBUG(DBG_ERROR, "reconfiguration of the tunnel failed");
++      }
++      if (update) {
++              DEBUG(DBG_INFO, "updating entry: %x", entry);
++              hashlist_reposition(bul.entries, (void *)entry,
++                                  entry->callback_time);
++      } else {
++              DEBUG(DBG_INFO, "adding entry: %x", entry);
++
++              hashkey.a1 = &entry->cn_addr;
++              hashkey.a2 = &entry->home_addr;
++
++              if ((hashlist_add(bul.entries, &hashkey,
++                                entry->callback_time,
++                                entry)) < 0) {
++                      DEBUG(DBG_ERROR, "Hash add failed");
++                      mipv6_bul_entry_free(entry);                    
++                      return NULL;
++              }
++      }
++      timer_update(); 
++
++      return entry;
++}
++
++/**
++ * mipv6_bul_delete - delete Binding Update List entry
++ * @cn_addr: address for entry to delete
++ *
++ * Deletes the entry for @cn_addr from the Binding Update List.
++ * Returns zero if entry was deleted succesfully, otherwise returns
++ * negative.  Caller may not hold @bul_lock.
++ **/
++int mipv6_bul_delete(struct in6_addr *cn_addr, struct in6_addr *home_addr)
++{
++      struct mipv6_bul_entry *entry;
++      struct in6_addr_pair hashkey;
++
++      DEBUG_FUNC();
++
++      hashkey.a1 = cn_addr;
++      hashkey.a2 = home_addr;
++
++      write_lock(&bul_lock);
++
++      if (unlikely(bul.entries == NULL) ||  
++          (entry = (struct mipv6_bul_entry *)
++           hashlist_get(bul.entries, &hashkey)) == NULL) {
++              write_unlock(&bul_lock);
++              DEBUG(DBG_INFO, "No such entry");
++              return -ENOENT;
++      }
++
++      hashlist_delete(bul.entries, (void *)entry);
++
++      del_bul_entry_tnl(entry);
++
++      mipv6_bul_entry_free(entry);
++      timer_update();
++      write_unlock(&bul_lock);
++
++      DEBUG(DBG_INFO, "Binding update list entry deleted");
++
++      return 0;
++}
++
++/**********************************************************************
++ *
++ * Proc interface functions
++ *
++ **********************************************************************/
++
++#define BUL_INFO_LEN 152
++
++struct procinfo_iterator_args {
++      char *buffer;
++      int offset;
++      int length;
++      int skip;
++      int len;
++};
++
++static int procinfo_iterator(void *data, void *args,
++                           unsigned long *sortkey)
++{
++      struct procinfo_iterator_args *arg =
++              (struct procinfo_iterator_args *)args;
++      struct mipv6_bul_entry *entry =
++              (struct mipv6_bul_entry *)data;
++      unsigned long callback_seconds;
++
++      DEBUG_FUNC();
++
++      if (entry == NULL) return ITERATOR_ERR;
++
++      if (time_after(jiffies, entry->callback_time))
++              callback_seconds = 0;
++      else
++              callback_seconds = (entry->callback_time - jiffies) / HZ;
++
++      if (arg->skip < arg->offset / BUL_INFO_LEN) {
++              arg->skip++;
++              return ITERATOR_CONT;
++      }
++
++      if (arg->len >= arg->length)
++              return ITERATOR_CONT;
++
++      /* CN HoA CoA ExpInSecs SeqNum State Delay MaxDelay CallbackInSecs */
++      arg->len += sprintf(arg->buffer + arg->len,
++                          "%08x%08x%08x%08x %08x%08x%08x%08x %08x%08x%08x%08x\n"
++                          "%010lu %05d %02d %010d %010d %010lu\n",
++                          ntohl(entry->cn_addr.s6_addr32[0]),
++                          ntohl(entry->cn_addr.s6_addr32[1]),
++                          ntohl(entry->cn_addr.s6_addr32[2]),
++                          ntohl(entry->cn_addr.s6_addr32[3]),
++                          ntohl(entry->home_addr.s6_addr32[0]),
++                          ntohl(entry->home_addr.s6_addr32[1]),
++                          ntohl(entry->home_addr.s6_addr32[2]),
++                          ntohl(entry->home_addr.s6_addr32[3]),
++                          ntohl(entry->coa.s6_addr32[0]),
++                          ntohl(entry->coa.s6_addr32[1]),
++                          ntohl(entry->coa.s6_addr32[2]),
++                          ntohl(entry->coa.s6_addr32[3]),
++                          (entry->expire - jiffies) / HZ,
++                          entry->seq, entry->state, entry->delay, 
++                          entry->maxdelay, callback_seconds);
++
++      return ITERATOR_CONT;
++}
++
++
++/*
++ * Callback function for proc filesystem.
++ */
++static int bul_proc_info(char *buffer, char **start, off_t offset,
++                            int length)
++{
++      struct procinfo_iterator_args args;
++
++      DEBUG_FUNC();
++
++      args.buffer = buffer;
++      args.offset = offset;
++      args.length = length;
++      args.skip = 0;
++      args.len = 0;
++
++      read_lock_bh(&bul_lock);
++      hashlist_iterate(bul.entries, &args, procinfo_iterator);
++      read_unlock_bh(&bul_lock);
++
++      *start = buffer;
++      if (offset)
++              *start += offset % BUL_INFO_LEN;
++
++      args.len -= offset % BUL_INFO_LEN;
++
++      if (args.len > length)
++              args.len = length;
++      if (args.len < 0)
++              args.len = 0;
++      
++      return args.len;
++}
++
++/**********************************************************************
++ *
++ * Code module init/fini functions
++ *
++ **********************************************************************/
++
++int __init mipv6_bul_init(__u32 size)
++{
++      DEBUG_FUNC();
++
++      if (size < 1) {
++              DEBUG(DBG_CRITICAL, 
++                    "Binding update list size must be at least 1");
++              return -EINVAL;
++      }
++      bul.entries = hashlist_create(MIPV6_BUL_HASHSIZE, size, 
++                                     sizeof(struct mipv6_bul_entry),
++                                     "mip6_bul", NULL, NULL,
++                                     bul_compare, bul_hash);
++
++      if (bul.entries == NULL) {
++              DEBUG(DBG_CRITICAL, "Couldn't allocate memory for "
++                    "hashlist when creating a binding update list");
++              return -ENOMEM;
++      }
++      init_timer(&bul.callback_timer);
++      bul.callback_timer.data = 0;
++      bul.callback_timer.function = timer_handler;
++      proc_net_create("mip6_bul", 0, bul_proc_info);
++      DEBUG(DBG_INFO, "Binding update list initialized");
++      return 0;
++}
++
++void __exit mipv6_bul_exit()
++{
++      struct mipv6_bul_entry *entry;
++      struct hashlist *entries;
++
++      DEBUG_FUNC();
++
++      proc_net_remove("mip6_bul");
++
++      write_lock_bh(&bul_lock);
++
++      DEBUG(DBG_INFO, "Stopping the bul timer");
++      del_timer(&bul.callback_timer);
++
++      while ((entry = (struct mipv6_bul_entry *) 
++              hashlist_get_first(bul.entries)) != NULL) {
++              hashlist_delete(bul.entries, (void *)entry);
++              
++              del_bul_entry_tnl(entry);
++              
++              mipv6_bul_entry_free(entry);
++      }
++      entries = bul.entries;
++      bul.entries = NULL;
++      write_unlock_bh(&bul_lock); 
++
++      hashlist_destroy(entries);
++
++      DEBUG(DBG_INFO, "binding update list destroyed");
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/bul.h linux-2.4.25/net/ipv6/mobile_ip6/bul.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/bul.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/bul.h     2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,91 @@
++/*
++ *      MIPL Mobile IPv6 Binding Update List header file
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _BUL_H
++#define _BUL_H
++
++#include "hashlist.h"
++
++#define ACK_OK          0x01
++#define RESEND_EXP      0x02
++#define ACK_ERROR       0x04
++
++#define HOME_RESEND_EXPIRE 3600
++#define MIPV6_COOKIE_LEN 8
++struct mipv6_rr_info {
++      /* RR information */
++      u16 rr_state;                /* State of the RR */
++      u16 rr_flags;                /* Flags for the RR */
++      u8 hot_cookie[MIPV6_COOKIE_LEN];    /* HoT Cookie */
++      u8 cot_cookie[MIPV6_COOKIE_LEN];    /* CoT Cookie */
++      u8 home_cookie[MIPV6_COOKIE_LEN];   /* Home Cookie */
++      u8 careof_cookie[MIPV6_COOKIE_LEN]; /* Careof Cookie */
++      u32 lastsend_hoti;            /* When HoTI was last sent (jiffies) */
++      u32 lastsend_coti;            /* When CoTI was last sent (jiffies) */
++      u32 home_time;                /* when Care-of cookie was received */
++      u32 careof_time;              /* when Home cookie was received */
++      int home_nonce_index;         /* Home cookie nonce index */
++      int careof_nonce_index;       /* Care-of cookie nonce index */
++      u8 *kbu;                      /* Binding authentication key */
++};
++struct mipv6_bul_entry {
++      struct hashlist_entry e;
++      struct in6_addr cn_addr;        /* CN to which BU was sent */
++      struct in6_addr home_addr;      /* home address of this binding */
++      struct in6_addr coa;            /* care-of address of the sent BU */
++
++      unsigned long expire;           /* entry's expiration time (jiffies) */ 
++      __u32 lifetime;                 /* lifetime sent in this BU */
++      __u32 lastsend;                 /* last time when BU sent (jiffies) */
++      __u32 consecutive_sends;        /* Number of consecutive BU's sent */
++      __u16 seq;                      /* sequence number of the latest BU */
++      __u8 flags;                     /* BU send flags */
++      __u8 state;                     /* resend state */
++      __u32 initdelay;                /* initial ack wait */
++      __u32 delay;                    /* current ack wait */
++      __u32 maxdelay;                 /* maximum ack wait */
++
++      struct mipv6_rr_info *rr;
++      struct mipv6_mh_opt *ops;       /* saved option values */
++
++      unsigned long callback_time;
++      int (*callback)(struct mipv6_bul_entry *entry);
++};
++
++extern rwlock_t bul_lock;
++
++int mipv6_bul_init(__u32 size);
++
++void mipv6_bul_exit(void);
++
++struct mipv6_bul_entry *mipv6_bul_add(
++      struct in6_addr *cn_addr, struct in6_addr *home_addr,
++      struct in6_addr *coa, __u32 lifetime, __u16 seq, __u8 flags,
++      int (*callback)(struct mipv6_bul_entry *entry), __u32 callback_time,
++      __u8 state, __u32 delay, __u32 maxdelay, struct mipv6_mh_opt *ops,
++      struct mipv6_rr_info *rr);
++
++int mipv6_bul_delete(struct in6_addr *cn_addr, struct in6_addr *home_addr);
++
++int mipv6_bul_exists(struct in6_addr *cnaddr, struct in6_addr *home_addr);
++
++struct mipv6_bul_entry *mipv6_bul_get(struct in6_addr *cnaddr,
++                                    struct in6_addr *home_addr);
++struct mipv6_bul_entry *mipv6_bul_get_by_ccookie(struct in6_addr *cn_addr,
++                                               u8 *cookie);
++
++int bul_entry_expired(struct mipv6_bul_entry *bulentry);
++
++void mipv6_bul_reschedule(struct mipv6_bul_entry *entry);
++
++int mipv6_bul_iterate(int (*func)(void *, void *, unsigned long *), void *args);
++
++#endif /* BUL_H */
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/config.h linux-2.4.25/net/ipv6/mobile_ip6/config.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/config.h      1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/config.h  2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,72 @@
++/*
++ * Configuration parameters
++ *
++ * $Id$
++ */
++
++#define MIPV6VERSION "D24"
++#define MIPLVERSION "v1.0"
++
++#define CAP_CN        0x01
++#define CAP_HA        0x02
++#define CAP_MN        0x04
++
++struct mip6_conf {
++      int capabilities;
++      int debug_level;
++      int accept_ret_rout;
++      int max_rtr_reachable_time;
++      int eager_cell_switching;
++      int max_num_tunnels;
++      int min_num_tunnels;
++      int binding_refresh_advice;
++      int bu_lladdr;
++      int bu_keymgm;
++      int bu_cn_ack;
++};
++
++extern struct mip6_conf mip6node_cnf;
++
++struct mipv6_bce;
++
++struct mip6_func {
++      void (*bce_home_add) (int ifindex, struct in6_addr *daddr, 
++                            struct in6_addr *haddr, struct in6_addr *coa,
++                            struct in6_addr *rep_coa, __u32 lifetime, 
++                            __u16 sequence, __u8 flags, __u8 *k_bu);
++      void (*bce_cache_add) (int ifindex, struct in6_addr *daddr,
++                             struct in6_addr *haddr, struct in6_addr *coa,
++                             struct in6_addr *rep_coa, __u32 lifetime,
++                             __u16 sequence, __u8 flags, __u8 *k_bu);
++      void (*bce_home_del) (struct in6_addr *daddr, struct in6_addr *haddr, 
++                            struct in6_addr *coa, struct in6_addr *rep_coa,
++                            __u16 sequence, __u8 flags,
++                            __u8 *k_bu);
++      void (*bce_cache_del) (struct in6_addr *daddr, struct in6_addr *haddr, 
++                             struct in6_addr *coa, struct in6_addr *rep_coa,
++                             __u16 sequence, __u8 flags,
++                             __u8 *k_bu);
++      
++      int (*bce_tnl_rt_add) (struct in6_addr *coa, 
++                             struct in6_addr *ha_addr, 
++                             struct in6_addr *home_addr);
++
++      void (*bce_tnl_rt_del) (struct in6_addr *coa, 
++                              struct in6_addr *ha_addr, 
++                              struct in6_addr *home_addr);
++
++      void (*proxy_del) (struct in6_addr *home_addr, struct mipv6_bce *entry);
++      int (*proxy_create) (int flags, int ifindex, struct in6_addr *coa,
++                           struct in6_addr *our_addr, struct in6_addr *home_addr);
++
++      int (*icmpv6_dhaad_rep_rcv) (struct sk_buff *skb);
++      int (*icmpv6_dhaad_req_rcv) (struct sk_buff *skb);
++      int (*icmpv6_pfxadv_rcv) (struct sk_buff *skb);
++      int (*icmpv6_pfxsol_rcv) (struct sk_buff *skb);
++      int (*icmpv6_paramprob_rcv) (struct sk_buff *skb);
++
++      int (*mn_use_hao) (struct in6_addr *daddr, struct in6_addr *saddr);
++      void (*mn_check_tunneled_packet) (struct sk_buff *skb);
++};
++
++extern struct mip6_func mip6_fn;
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/debug.h linux-2.4.25/net/ipv6/mobile_ip6/debug.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/debug.h       1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/debug.h   2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,112 @@
++/*
++ *      MIPL Mobile IPv6 Debugging macros and functions
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _DEBUG_H
++#define _DEBUG_H
++
++#include <linux/autoconf.h>
++
++/* priorities for different debug conditions */
++
++#define DBG_CRITICAL   0 /* unrecoverable error                     */
++#define DBG_ERROR      1 /* error (recoverable)                     */
++#define DBG_WARNING    2 /* unusual situation but not a real error  */
++#define DBG_INFO       3 /* generally useful information            */
++#define DBG_EXTRA      4 /* extra information                       */
++#define DBG_FUNC_ENTRY 6 /* use to indicate function entry and exit */
++#define DBG_DATADUMP   7 /* packet dumps, etc. lots of flood        */
++
++/**
++ * NIPV6ADDR - macro for IPv6 addresses
++ * @addr: Network byte order IPv6 address
++ *
++ * Macro for printing IPv6 addresses.  Used in conjunction with
++ * printk() or derivatives (such as DEBUG macro).
++ **/
++#define NIPV6ADDR(addr) \
++        ntohs(((u16 *)addr)[0]), \
++        ntohs(((u16 *)addr)[1]), \
++        ntohs(((u16 *)addr)[2]), \
++        ntohs(((u16 *)addr)[3]), \
++        ntohs(((u16 *)addr)[4]), \
++        ntohs(((u16 *)addr)[5]), \
++        ntohs(((u16 *)addr)[6]), \
++        ntohs(((u16 *)addr)[7])
++
++#ifdef CONFIG_IPV6_MOBILITY_DEBUG
++extern int mipv6_debug;
++
++/**
++ * debug_print - print debug message
++ * @debug_level: message priority
++ * @fname: calling function's name
++ * @fmt: printf-style formatting string
++ *
++ * Prints a debug message to system log if @debug_level is less or
++ * equal to @mipv6_debug.  Should always be called using DEBUG()
++ * macro, not directly.
++ **/
++static void debug_print(int debug_level, const char *fname, const char* fmt, ...)
++{
++      char s[1024];
++      va_list args;
++ 
++      if (mipv6_debug < debug_level)
++              return;
++ 
++      va_start(args, fmt);
++      vsprintf(s, fmt, args);
++      printk("mip6[%s]: %s\n", fname, s);
++      va_end(args);
++}
++
++/**
++ * debug_print_buffer - print arbitrary buffer to system log
++ * @debug_level: message priority
++ * @data: pointer to buffer
++ * @len: number of bytes to print
++ *
++ * Prints @len bytes from buffer @data to system log.  @debug_level
++ * tells on which debug level message gets printed.  For
++ * debug_print_buffer() priority %DBG_DATADUMP should be used.
++ **/
++#define debug_print_buffer(debug_level,data,len) { \
++      if (mipv6_debug >= debug_level) { \
++      int i; \
++      for (i=0; i<len; i++) { \
++              if (i%16 == 0) printk("\n%04x: ", i); \
++              printk("%02x ", ((unsigned char *)data)[i]); \
++      } \
++      printk("\n\n"); \
++      } \
++}
++
++#define DEBUG(x,y,z...) debug_print(x,__FUNCTION__,y,##z)
++#define DEBUG_FUNC() \
++DEBUG(DBG_FUNC_ENTRY, "%s(%d)/%s: ", __FILE__,__LINE__,__FUNCTION__)
++
++#else
++#define DEBUG(x,y,z...)
++#define DEBUG_FUNC()
++#define debug_print_buffer(x,y,z)
++#endif
++
++#undef ASSERT
++#define ASSERT(expression) { \
++        if (!(expression)) { \
++                (void)printk(KERN_ERR \
++                 "Assertion \"%s\" failed: file \"%s\", function \"%s\", line %d\n", \
++                 #expression, __FILE__, __FUNCTION__, __LINE__); \
++              BUG(); \
++        } \
++}
++
++#endif /* _DEBUG_H */
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/exthdrs.c linux-2.4.25/net/ipv6/mobile_ip6/exthdrs.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/exthdrs.c     1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/exthdrs.c 2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,394 @@
++/*
++ *    Extension Header handling and adding code
++ *
++ *    Authors:
++ *    Sami Kivisaari          <skivisaa@cc.hut.fi>    
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ */
++
++#include <linux/types.h>
++#include <linux/slab.h>
++
++#include <net/ipv6.h>
++#include <net/ip6_route.h>
++#include <net/addrconf.h>
++#include <net/mipv6.h>
++
++#include "debug.h"
++#include "stats.h"
++#include "mobhdr.h"
++#include "bcache.h"
++#include "config.h"
++
++/**
++ * mipv6_append_home_addr - Add Home Address Option
++ * @opt: buffer for Home Address Option
++ * @offset: offset from beginning of @opt
++ * @addr: address for HAO
++ *
++ * Adds a Home Address Option to a packet.  Option is stored in
++ * @offset from beginning of @opt.  The option is created but the
++ * original source address in IPv6 header is left intact.  The source
++ * address will be changed from home address to CoA after the checksum
++ * has been calculated in getfrag.  Padding is done automatically, and
++ * @opt must have allocated space for both actual option and pad.
++ * Returns offset from @opt to end of options.
++ **/
++int mipv6_append_home_addr(__u8 *opt, int offset, struct in6_addr *addr)
++{
++      int pad;
++      struct mipv6_dstopt_homeaddr *ho;
++
++      DEBUG(DBG_DATADUMP, "HAO: %x:%x:%x:%x:%x:%x:%x:%x",
++            NIPV6ADDR(addr));
++
++      pad = (6 - offset) & 7;
++      mipv6_add_pad(opt + offset, pad);
++
++      ho = (struct mipv6_dstopt_homeaddr *)(opt + offset + pad);
++      ho->type = MIPV6_TLV_HOMEADDR;
++      ho->length = sizeof(*ho) - 2;
++      ipv6_addr_copy(&ho->addr, addr); 
++
++      return offset + pad + sizeof(*ho);
++}
++static inline int check_hao_validity(struct mipv6_dstopt_homeaddr *haopt, 
++                                   u8 *dst1, 
++                                   struct in6_addr *saddr, 
++                                   struct in6_addr *daddr)
++{
++      int addr_type = ipv6_addr_type(&haopt->addr);
++      struct mipv6_bce bc_entry;
++      
++      if (addr_type & IPV6_ADDR_LINKLOCAL || 
++          !(addr_type & IPV6_ADDR_UNICAST)) {
++              DEBUG(DBG_INFO, "HAO with link local or non-unicast HoA, "
++                    "not sending BE to "
++                    "home address " 
++                    "%x:%x:%x:%x:%x:%x:%x:%x ",
++                    "care-of  address %x:%x:%x:%x:%x:%x:%x:%x",
++                    NIPV6ADDR(&haopt->addr),
++                    NIPV6ADDR(saddr));
++              return -EINVAL;
++      } else if (dst1[0] != IPPROTO_MOBILITY && 
++          (mipv6_bcache_get(&haopt->addr, 
++                            daddr, &bc_entry) != 0 ||
++           ipv6_addr_cmp(saddr, &bc_entry.coa))) {
++              DEBUG(DBG_INFO, "HAO without binding or incorrect CoA, "
++                    "sending BE code 1: "
++                    "home address %x:%x:%x:%x:%x:%x:%x:%x",
++                    "to care-of address %x:%x:%x:%x:%x:%x:%x:%x",
++                    NIPV6ADDR(&haopt->addr),
++                    NIPV6ADDR(saddr));
++              return -ENOENT;
++      }
++      return 0;
++}
++/**
++ * mipv6_handle_homeaddr - Home Address Destination Option handler
++ * @skb: packet buffer
++ * @optoff: offset to where option begins
++ *
++ * Handles Home Address Option in IPv6 Destination Option header.
++ * Packet and offset to option are passed.  If HAO is used without
++ * binding, sends a Binding Error code 1.  When sending BE, notify bit
++ * is cleared to prevent IPv6 error handling from sending ICMP
++ * Parameter Problem.  Returns 1 on success, otherwise zero.
++ **/
++int mipv6_handle_homeaddr(struct sk_buff *skb, int optoff)
++{
++      struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
++      struct in6_addr coaddr;
++      struct inet6_skb_parm *opt = (struct inet6_skb_parm *) skb->cb;
++      struct mipv6_dstopt_homeaddr *haopt =
++          (struct mipv6_dstopt_homeaddr *) &skb->nh.raw[optoff];
++      u8 *dst1;
++      int err;
++
++      DEBUG_FUNC();
++      
++      if (haopt->length != sizeof(*haopt) - 2) {
++              DEBUG(DBG_WARNING, "HAO has invalid length");
++              MIPV6_INC_STATS(n_ha_drop.invalid);
++              return 0;
++      }
++      dst1 = (u8 *)skb->h.raw;
++      err = check_hao_validity(haopt, dst1, saddr, &skb->nh.ipv6h->daddr);
++
++      if (err) {
++              haopt->type &= ~(0x80); /* clear notify bit */
++              if (err == -ENOENT)
++                      mipv6_send_be(&skb->nh.ipv6h->daddr, saddr,
++                                    &haopt->addr, MIPV6_BE_HAO_WO_BINDING);
++              MIPV6_INC_STATS(n_ha_drop.misc);
++              return 0;
++      }
++      ipv6_addr_copy(&coaddr, saddr);
++      ipv6_addr_copy(saddr, &haopt->addr);
++      ipv6_addr_copy(&haopt->addr, &coaddr);
++      opt->hao = optoff;
++      if (mip6_fn.mn_check_tunneled_packet != NULL)
++              mip6_fn.mn_check_tunneled_packet(skb);
++
++      MIPV6_INC_STATS(n_ha_rcvd);
++      return 1;
++}
++
++/**
++ * mipv6_icmp_swap_addrs - Switch HAO and src and RT2 and dest for ICMP errors
++ * @skb: packet buffer
++ *
++ * Reset the source address and the Home Address option in skb before
++ * appending it to an ICMP error message, so original packet appears
++ * in the error message rather than mangled.
++ **/
++void mipv6_icmp_swap_addrs(struct sk_buff *skb)
++{
++      struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
++      struct in6_addr tmp;
++      struct in6_addr *hoa;
++      DEBUG_FUNC();
++      if (opt->srcrt2) {
++              struct rt2_hdr *rt2;
++              rt2 = (struct rt2_hdr *)(skb->nh.raw + opt->srcrt2);
++              hoa = &rt2->addr;
++
++              ipv6_addr_copy(&tmp, hoa);
++              ipv6_addr_copy(hoa, &skb->nh.ipv6h->daddr);
++              ipv6_addr_copy(&skb->nh.ipv6h->daddr, &tmp);
++              rt2->rt_hdr.segments_left++;
++              skb->nh.ipv6h->hop_limit++;
++      }
++      if (opt->hao) {
++              struct mipv6_dstopt_homeaddr *hao;
++              hao = (struct mipv6_dstopt_homeaddr *)(skb->nh.raw + opt->hao);
++              hoa = &hao->addr;
++
++              ipv6_addr_copy(&tmp, hoa);
++              ipv6_addr_copy(hoa, &skb->nh.ipv6h->saddr);
++              ipv6_addr_copy(&skb->nh.ipv6h->saddr, &tmp);
++      }
++}
++
++/**
++ * mipv6_append_rt2hdr - Add Type 2 Routing Header
++ * @rt: buffer for new routing header
++ * @addr: intermediate hop address
++ *
++ * Adds a Routing Header Type 2 in a packet.  Stores newly created
++ * routing header in buffer @rt.  Type 2 RT only carries one address,
++ * so there is no need to process old routing header.  @rt must have
++ * allocated space for 24 bytes.
++ **/
++void mipv6_append_rt2hdr(struct ipv6_rt_hdr *rt, struct in6_addr *addr)
++{
++      struct rt2_hdr *rt2 = (struct rt2_hdr *)rt;
++
++        DEBUG(DBG_DATADUMP, "RT2: %x:%x:%x:%x:%x:%x:%x:%x",
++            NIPV6ADDR(addr));
++
++      if (ipv6_addr_type(addr) == IPV6_ADDR_MULTICAST) {
++              DEBUG(DBG_ERROR, "destination address not unicast");
++              return;
++      }
++
++      memset(rt2, 0, sizeof(*rt2));
++      rt2->rt_hdr.type = 2;
++      rt2->rt_hdr.hdrlen = 2;
++      rt2->rt_hdr.segments_left = 1;
++      ipv6_addr_copy(&rt2->addr, addr);
++}
++
++/**
++ * mipv6_append_dst1opts - Add Destination Option (1) Headers
++ * @dst1opt: buffer for new destination options
++ * @saddr: address for Home Address Option
++ * @old_dst1opt: old destination options
++ * @len: length of options
++ *
++ * Adds Destination Option (1) Header to a packet.  New options are
++ * stored in @dst1opt.  If old destination options exist, they are
++ * copied from @old_dst1opt.  Only Home Address Option is destination
++ * option.  @dstopt must have allocated space for @len bytes.  @len
++ * includes Destination Option Header (2 bytes), Home Address Option
++ * (18 bytes) and possible HAO pad (8n+6).
++ **/
++/*
++ * ISSUE: Home Address Destination Option should really be added to a
++ * new destination option header specified in Mobile IPv6 spec which
++ * should be placed after routing header(s), but before fragmentation
++ * header.  Putting HAO in DO1 works for now, but support for the new
++ * placement should be added to the IPv6 stack.
++ */
++void 
++mipv6_append_dst1opts(struct ipv6_opt_hdr *dst1opt, struct in6_addr *saddr,
++                    struct ipv6_opt_hdr *old_dst1opt, int len)
++{
++      int offset;
++
++      if (old_dst1opt) {
++              memcpy(dst1opt, old_dst1opt, ipv6_optlen(old_dst1opt));
++              offset = ipv6_optlen(old_dst1opt);
++      } else {
++              offset = sizeof (*dst1opt);
++      }
++      dst1opt->hdrlen = (len >> 3) - 1;
++      mipv6_append_home_addr((__u8 *) dst1opt, offset, saddr);
++}
++
++/**
++ * mipv6_modify_txoptions - Modify outgoing packets
++ * @sk: socket
++ * @skb: packet buffer for outgoing packet
++ * @old_opt: transmit options
++ * @fl: packet flow structure
++ * @dst: pointer to destination cache entry
++ *
++ * Adds Home Address Option (for MN packets, when not at home) and
++ * Routing Header Type 2 (for CN packets when sending to an MN) to
++ * data packets.  Old extension headers are copied from @old_opt (if
++ * any).  Extension headers are _explicitly_ added for packets with
++ * Mobility Header.  Returns the new header structure, or old if no
++ * changes.
++ **/
++struct ipv6_txoptions *
++mipv6_modify_txoptions(struct sock *sk, struct sk_buff *skb, 
++                     struct ipv6_txoptions *old_opt, struct flowi *fl, 
++                     struct dst_entry **dst)
++{     
++      struct ipv6_opt_hdr *old_hopopt = NULL;
++      struct ipv6_opt_hdr *old_dst1opt = NULL;
++      struct ipv6_rt_hdr *old_srcrt = NULL;
++
++      int srcrtlen = 0, dst1len = 0;
++      int tot_len, use_hao = 0;
++      struct ipv6_txoptions *opt;
++      struct mipv6_bce bc_entry;
++      struct in6_addr tmpaddr, *saddr, *daddr, coaddr;
++      __u8 *opt_ptr;
++
++      DEBUG_FUNC();
++
++      if (fl->proto == IPPROTO_MOBILITY) return old_opt;
++      /*
++       * we have to be prepared to the fact that saddr might not be present,
++       * if that is the case, we acquire saddr just as kernel does.
++       */
++      saddr = fl ? fl->fl6_src : NULL;
++      daddr = fl ? fl->fl6_dst : NULL;
++
++      if (daddr == NULL)
++              return old_opt;
++      if (saddr == NULL) {
++              int err = ipv6_get_saddr(NULL, daddr, &tmpaddr);
++              if (err)
++                      return old_opt;
++              else
++                      saddr = &tmpaddr;
++      }
++
++      DEBUG(DBG_DATADUMP,
++            "dest. address of packet: %x:%x:%x:%x:%x:%x:%x:%x",
++            NIPV6ADDR(daddr));
++      DEBUG(DBG_DATADUMP, " and src. address: %x:%x:%x:%x:%x:%x:%x:%x", 
++            NIPV6ADDR(saddr));
++
++      if (old_opt) {
++              old_hopopt = old_opt->hopopt;
++              old_dst1opt = old_opt->dst1opt;
++              old_srcrt = old_opt->srcrt;
++      } 
++
++      if (mip6_fn.mn_use_hao != NULL)
++              use_hao = mip6_fn.mn_use_hao(daddr, saddr);
++
++      if (use_hao) {
++              if (old_dst1opt)
++                      dst1len = ipv6_optlen(old_dst1opt);
++              dst1len += sizeof(struct mipv6_dstopt_homeaddr) +
++                      ((6 - dst1len) & 7); /* padding */
++      }
++
++      if (mipv6_bcache_get(daddr, saddr, &bc_entry) == 0)
++              srcrtlen = sizeof(struct rt2_hdr);
++
++      if ((tot_len = srcrtlen + dst1len) == 0) { 
++              return old_opt;
++      }
++
++      tot_len += sizeof(*opt);
++
++      if (!(opt = kmalloc(tot_len, GFP_ATOMIC))) {
++              return NULL;
++      }
++      memset(opt, 0, tot_len);
++      opt->tot_len = tot_len;
++      opt_ptr = (__u8 *) (opt + 1);
++      
++      if (old_srcrt) {
++              opt->srcrt = old_srcrt;
++              opt->opt_nflen += ipv6_optlen(old_srcrt);
++      }
++
++      if (srcrtlen) {
++              DEBUG(DBG_DATADUMP, "Binding exists. Adding routing header");
++
++              opt->srcrt2 = (struct ipv6_rt_hdr *) opt_ptr;
++              opt->opt_nflen += srcrtlen;
++              opt_ptr += srcrtlen;
++              
++              /*
++               * Append care-of-address to routing header (original
++               * destination address is home address, the first
++               * source route segment gets put to the destination
++               * address and the home address gets to the last
++               * segment of source route (just as it should)) 
++               */
++
++              ipv6_addr_copy(&coaddr, &bc_entry.coa);
++
++              mipv6_append_rt2hdr(opt->srcrt2, &coaddr);
++
++              /*
++               * reroute output (we have to do this in case of TCP
++                 * segment) unless a routing header of type 0 is also added
++               */
++              if (dst && !opt->srcrt) {
++                      struct in6_addr *tmp = fl->fl6_dst;
++                      fl->fl6_dst = &coaddr;
++
++                      dst_release(*dst);
++                      *dst = ip6_route_output(sk, fl);
++                      if (skb)
++                              skb->dst = *dst;
++                      fl->fl6_dst = tmp;
++
++                      DEBUG(DBG_DATADUMP, "Rerouted outgoing packet");
++              }
++      }
++
++      /* Only home address option is inserted to first dst opt header */
++      if (dst1len) {
++              opt->dst1opt = (struct ipv6_opt_hdr *) opt_ptr;
++              opt->opt_flen += dst1len;
++              opt_ptr += dst1len;
++              mipv6_append_dst1opts(opt->dst1opt, saddr, 
++                                    old_dst1opt, dst1len);
++              opt->mipv6_flags = MIPV6_SND_HAO;
++      } else if (old_dst1opt) {
++              opt->dst1opt = old_dst1opt;
++              opt->opt_flen += ipv6_optlen(old_dst1opt);
++      }
++      if (old_hopopt) {
++              opt->hopopt = old_hopopt;
++              opt->opt_nflen += ipv6_optlen(old_hopopt);
++      }       
++      
++      return opt;
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/exthdrs.h linux-2.4.25/net/ipv6/mobile_ip6/exthdrs.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/exthdrs.h     1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/exthdrs.h 2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,47 @@
++/*
++ *    MIPL Mobile IPv6 Extension Headers header file
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ */
++
++#ifndef _MIPV6_EXTHDRS_H
++#define _MIPV6_EXTHDRS_H
++
++struct in6_addr;
++struct sk_buff;
++struct ipv6_rt_hdr;
++struct ipv6_opt_hdr;
++struct ipv6_txoptions;
++struct flowi;
++struct dst_entry;
++/*
++ * Home Address Destination Option function prototypes
++ */
++int mipv6_append_home_addr(__u8 *opt, int offset, struct in6_addr *addr);
++
++int mipv6_handle_homeaddr(struct sk_buff *skb, int optoff);
++
++void mipv6_icmp_swap_addrs(struct sk_buff *skb);
++
++/*
++ * Creates a routing header of type 2.
++ */
++void mipv6_append_rt2hdr(struct ipv6_rt_hdr *srcrt, struct in6_addr *addr);
++
++/* Function to add the first destination option header, which may
++ * include a home address option.  
++ */
++void mipv6_append_dst1opts(struct ipv6_opt_hdr *dst1opt, struct in6_addr *saddr,
++                         struct ipv6_opt_hdr *old_dst1opt, int len);
++
++struct ipv6_txoptions *mipv6_modify_txoptions(
++      struct sock *sk, struct sk_buff *skb,
++      struct ipv6_txoptions *old_opt, struct flowi *fl,
++      struct dst_entry **dst);
++
++#endif /* _MIPV6_EXTHDRS_H */
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/ha.c linux-2.4.25/net/ipv6/mobile_ip6/ha.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/ha.c  1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/ha.c      2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,553 @@
++/*
++ *      Home-agent functionality
++ *
++ *      Authors:
++ *      Sami Kivisaari           <skivisaa@cc.hut.fi>
++ *      Henrik Petander          <lpetande@cc.hut.fi>
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ *   
++ *      Changes: Venkata Jagana,
++ *               Krishna Kumar     : Statistics fix
++ *               Masahide Nakamura : Use of mipv6_forward  
++ *     
++ */
++
++#include <linux/autoconf.h>
++#include <linux/net.h>
++#include <linux/skbuff.h>
++#include <linux/if_ether.h>
++#include <linux/netdevice.h>
++#include <linux/in6.h>
++#include <linux/init.h>
++#include <linux/netfilter.h>
++#include <linux/netfilter_ipv6.h>
++#ifdef CONFIG_SYSCTL
++#include <linux/sysctl.h>
++#endif
++
++#include <net/neighbour.h>
++#include <net/ipv6.h>
++#include <net/ip6_fib.h>
++#include <net/ip6_route.h>
++#include <net/ndisc.h>
++#include <net/addrconf.h>
++#include <net/neighbour.h>
++
++#include "tunnel_ha.h"
++#include "bcache.h"
++#include "stats.h"
++#include "debug.h"
++#include "util.h"
++#include "ha.h"
++#include "config.h"
++#include "mobhdr.h"
++
++static int mipv6_ha_tunnel_sitelocal = 0;
++
++#ifdef CONFIG_SYSCTL
++
++static struct ctl_table_header *mipv6_ha_sysctl_header;
++
++static struct mipv6_ha_sysctl_table
++{
++      struct ctl_table_header *sysctl_header;
++      ctl_table mipv6_vars[3];
++      ctl_table mipv6_mobility_table[2];
++      ctl_table mipv6_proto_table[2];
++      ctl_table mipv6_root_table[2];
++} mipv6_ha_sysctl = {
++      NULL,
++
++        {{NET_IPV6_MOBILITY_TUNNEL_SITELOCAL, "tunnel_sitelocal",
++        &mipv6_ha_tunnel_sitelocal, sizeof(int), 0644, NULL, 
++        &proc_dointvec},
++       {0}},
++
++      {{NET_IPV6_MOBILITY, "mobility", NULL, 0, 0555, 
++        mipv6_ha_sysctl.mipv6_vars}, {0}},
++      {{NET_IPV6, "ipv6", NULL, 0, 0555, 
++        mipv6_ha_sysctl.mipv6_mobility_table}, {0}},
++      {{CTL_NET, "net", NULL, 0, 0555, 
++        mipv6_ha_sysctl.mipv6_proto_table}, {0}}
++};
++
++#endif /* CONFIG_SYSCTL */
++
++
++/*  this is defined in kernel IPv6 module (sockglue.c)  */
++extern struct packet_type ipv6_packet_type;
++
++/* mipv6_forward: Intercept NS packets destined to home address of MN */
++int mipv6_forward(struct sk_buff *skb)
++{
++      struct ipv6hdr *ipv6h;
++      struct in6_addr *daddr, *saddr;
++      __u8 nexthdr;
++      int nhoff;
++      
++      if (skb == NULL) return  0;
++      
++      ipv6h = skb->nh.ipv6h;
++      daddr = &ipv6h->daddr;
++      saddr = &ipv6h->saddr;
++
++      nexthdr = ipv6h->nexthdr;
++      nhoff = sizeof(*ipv6h);
++   
++      if (ipv6_ext_hdr(nexthdr))
++              nhoff = ipv6_skip_exthdr(skb, nhoff, &nexthdr,
++                                       skb->len - sizeof(*ipv6h));
++      
++      /* Do not to forward Neighbor Solicitation to Home Address of MN */
++      if (nexthdr == IPPROTO_ICMPV6) {
++              struct icmp6hdr *icmp6h;
++              int dest_type;
++              
++              if (nhoff < 0 || !pskb_may_pull(skb, nhoff + 
++                                              sizeof(struct icmp6hdr))) {
++                      kfree_skb(skb);
++                      return 0;
++                   }
++              
++              dest_type = ipv6_addr_type(daddr);
++              icmp6h = (struct icmp6hdr *)&skb->nh.raw[nhoff];
++              
++              /* Intercepts NS to HoA of MN */
++
++              if ((icmp6h->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION) ||
++                  ((dest_type & IPV6_ADDR_MULTICAST) &&
++                   (icmp6h->icmp6_type == NDISC_ROUTER_ADVERTISEMENT))) {
++                      ip6_input(skb);
++              } else {
++                      ip6_forward(skb);
++              }
++      } else {
++              ip6_forward(skb);
++      }
++      return 0;
++}
++
++
++/**
++ * mipv6_proxy_nd_rem - stop acting as a proxy for @home_address
++ * @home_addr: address to remove
++ * @ha_addr: home agent's address on home link
++ * @linklocal: link-local compatibility bit
++ *
++ * When Home Agent acts as a proxy for an address it must leave the
++ * solicited node multicast group for that address and stop responding 
++ * to neighbour solicitations.  
++ **/
++static int mipv6_proxy_nd_rem(struct in6_addr *home_addr,
++                            int ifindex, int linklocal)
++{
++        /* When MN returns home HA leaves the solicited mcast groups
++         * for MNs home addresses 
++       */
++      int err;
++      struct net_device *dev;
++      
++      DEBUG_FUNC();
++      
++        if ((dev = dev_get_by_index(ifindex)) == NULL) {
++              DEBUG(DBG_ERROR, "couldn't get dev");
++              return -ENODEV;
++      }
++#if 1 /* TEST */
++      /* Remove link-local entry */
++      if (linklocal) {
++              struct in6_addr ll_addr;
++              mipv6_generate_ll_addr(&ll_addr, home_addr);
++              if ((err = pneigh_delete(&nd_tbl, &ll_addr, dev)) < 0) {
++                      DEBUG(DBG_INFO,
++                            "peigh_delete failed for "
++                            "%x:%x:%x:%x:%x:%x:%x:%x",
++                            NIPV6ADDR(&ll_addr));     
++              }
++      }
++#endif
++      /* Remove global (or site-local) entry */
++      if ((err = pneigh_delete(&nd_tbl, home_addr, dev)) < 0) {
++              DEBUG(DBG_INFO,
++                    "peigh_delete failed for " 
++                    "%x:%x:%x:%x:%x:%x:%x:%x",
++                    NIPV6ADDR(home_addr));
++      }
++      dev_put(dev);
++      return err;
++}
++
++/**
++ * mipv6_proxy_nd - join multicast group for this address
++ * @home_addr: address to defend
++ * @ha_addr: home agent's address on home link
++ * @linklocal: link-local compatibility bit
++ *
++ * While Mobile Node is away from home, Home Agent acts as a proxy for
++ * @home_address. HA responds to neighbour solicitations for  @home_address 
++ * thus getting all packets destined to home address of MN. 
++ **/
++static int mipv6_proxy_nd(struct in6_addr *home_addr, 
++                        int ifindex, int linklocal)
++{  
++      /* The HA sends a proxy ndisc_na message to all hosts on MN's
++       * home subnet by sending a neighbor advertisement with the
++       * home address or all addresses of the mobile node if the
++       * prefix is not 0. The addresses are formed by combining the
++       * suffix or the host part of the address with each subnet
++       * prefix that exists in the home subnet 
++       */
++      
++        /* Since no previous entry for MN exists a proxy_nd advertisement
++       * is sent to all nodes link local multicast address
++       */     
++      int err = -1;
++
++      struct net_device *dev;
++      struct in6_addr na_saddr;
++      struct in6_addr ll_addr;
++      struct pneigh_entry *ll_pneigh;
++      struct in6_addr mcdest;
++      int send_ll_na = 0;
++      int inc_opt = 1;
++      int solicited = 0;
++      int override = 1;
++      
++      DEBUG_FUNC();
++      
++      if ((dev = dev_get_by_index(ifindex)) == NULL) {
++              DEBUG(DBG_ERROR, "couldn't get dev");
++              return -ENODEV;
++      }
++      
++      if (!pneigh_lookup(&nd_tbl, home_addr, dev, 1)) {
++              DEBUG(DBG_INFO,
++                    "peigh_lookup failed for "
++                    "%x:%x:%x:%x:%x:%x:%x:%x",
++                    NIPV6ADDR(home_addr));
++              goto free_dev;
++      }
++#if 1 /* TEST */
++      if (linklocal) {
++              mipv6_generate_ll_addr(&ll_addr, home_addr);
++              
++              if ((ll_pneigh = pneigh_lookup(&nd_tbl, &ll_addr, 
++                                             dev, 1)) == NULL) {
++                      DEBUG(DBG_INFO,
++                            "peigh_lookup failed for "
++                            "%x:%x:%x:%x:%x:%x:%x:%x",
++                            NIPV6ADDR(&ll_addr));
++                      pneigh_delete(&nd_tbl, home_addr, dev);
++                      goto free_dev;
++              } else {
++                      send_ll_na = 1;
++              }
++      } else {
++              ll_pneigh = NULL;
++      }
++#endif        
++      /* Proxy neighbor advertisement of MN's home address 
++       * to all nodes solicited multicast address 
++       */
++      if (!ipv6_get_lladdr(dev, &na_saddr)) { 
++              ipv6_addr_all_nodes(&mcdest); 
++              ndisc_send_na(dev, NULL, &mcdest, home_addr, 0, 
++                            solicited, override, inc_opt);
++#if 1 /* TEST */
++              if (send_ll_na) {
++                      ndisc_send_na(dev, NULL, &mcdest, &ll_addr, 
++                                    0, solicited, override, inc_opt);
++              }
++#endif
++              err = 0;
++      } else {
++              DEBUG(DBG_ERROR, "failed to get link local address for sending proxy NA");
++      }
++free_dev:
++      dev_put(dev);
++      return err;
++      
++}
++
++struct inet6_ifaddr *is_on_link_ipv6_address(struct in6_addr *mn_haddr,
++                                           struct in6_addr *ha_addr)
++{
++      struct inet6_ifaddr *ifp;
++      struct inet6_dev *in6_dev;
++      struct inet6_ifaddr *oifp = NULL;
++
++      if ((ifp = ipv6_get_ifaddr(ha_addr, 0)) == NULL)
++              return NULL;
++
++      if ((in6_dev = ifp->idev) != NULL) {
++              in6_dev_hold(in6_dev);
++              oifp = in6_dev->addr_list;
++              while (oifp != NULL) {
++                      spin_lock(&oifp->lock);
++                      if (mipv6_prefix_compare(&oifp->addr, mn_haddr,
++                                               oifp->prefix_len) &&
++                          !(oifp->flags & IFA_F_TENTATIVE)) {
++                              spin_unlock(&oifp->lock);
++                              DEBUG(DBG_INFO, "Home Addr Opt: on-link");
++                              in6_ifa_hold(oifp);
++                              break;
++                      }
++                      spin_unlock(&oifp->lock);
++                      oifp = oifp->if_next;
++              }
++              in6_dev_put(in6_dev);
++      }
++      in6_ifa_put(ifp);
++/*      DEBUG(DBG_WARNING, "Home Addr Opt NOT on-link"); */
++      return oifp;
++
++}
++
++/*
++ * Lifetime checks. ifp->valid_lft >= ifp->prefered_lft always (see addrconf.c)
++ * Returned value is in seconds.
++ */
++
++static __u32 get_min_lifetime(struct inet6_ifaddr *ifp, __u32 lifetime)
++{
++      __u32 rem_lifetime = 0;
++      unsigned long now = jiffies;
++
++      if (ifp->valid_lft == 0) {
++              rem_lifetime = lifetime;
++      } else {
++              __u32 valid_lft_left =
++                  ifp->valid_lft - ((now - ifp->tstamp) / HZ);
++              rem_lifetime =
++                  min_t(unsigned long, valid_lft_left, lifetime);
++      }
++
++      return rem_lifetime;
++}
++
++#define MAX_LIFETIME 1000
++
++/**
++ * mipv6_lifetime_check - check maximum lifetime is not exceeded
++ * @lifetime: lifetime to check
++ *
++ * Checks @lifetime does not exceed %MAX_LIFETIME.  Returns @lifetime
++ * if not exceeded, otherwise returns %MAX_LIFETIME.
++ **/
++static int mipv6_lifetime_check(int lifetime)
++{
++      return (lifetime > MAX_LIFETIME) ? MAX_LIFETIME : lifetime;
++}
++
++/* Generic routine handling finish of BU processing */
++void mipv6_bu_finish(struct inet6_ifaddr *ifp, int ifindex, __u8 ba_status,
++                   struct in6_addr *daddr, struct in6_addr *haddr,
++                   struct in6_addr *coa, struct in6_addr *rep_coa,
++                   __u32 ba_lifetime, __u16 sequence, __u8 flags, __u8 *k_bu)
++{
++      int err;
++
++      if (ba_status >= REASON_UNSPECIFIED) {
++              /* DAD failed */
++              goto out;
++      }
++      
++      ba_lifetime = get_min_lifetime(ifp, ba_lifetime);
++      ba_lifetime = mipv6_lifetime_check(ba_lifetime);
++
++      if ((err = mipv6_bcache_add(ifindex, daddr, haddr, coa, 
++                                  ba_lifetime, sequence, flags,
++                                  HOME_REGISTRATION)) != 0 ) {
++              DEBUG(DBG_WARNING, "home reg failed.");
++
++              if (err == -ENOMEDIUM)
++                      return;
++
++              ba_status = INSUFFICIENT_RESOURCES;
++      } else {
++              DEBUG(DBG_INFO, "home reg succeeded.");
++      }
++
++      DEBUG(DBG_DATADUMP, "home_addr: %x:%x:%x:%x:%x:%x:%x:%x",
++            NIPV6ADDR(haddr));
++      DEBUG(DBG_DATADUMP, "coa: %x:%x:%x:%x:%x:%x:%x:%x",
++            NIPV6ADDR(coa));
++      DEBUG(DBG_DATADUMP, "lifet:%d, seq:%d", ba_lifetime, sequence);
++out:
++      mipv6_send_ba(daddr, haddr, coa, rep_coa, ba_status, sequence,
++                    ba_lifetime, k_bu);
++}
++
++static int ha_proxy_create(int flags, int ifindex, struct in6_addr *coa,
++                         struct in6_addr *our_addr, struct in6_addr *home_addr)
++{
++      int ret;
++
++      if ((ret = mipv6_add_tnl_to_mn(coa, our_addr, home_addr)) <= 0) {
++              if (ret != -ENOMEDIUM) {
++                      DEBUG(DBG_ERROR, "unable to configure tunnel to MN!");
++              }
++              return -1;
++      }
++      if (mipv6_proxy_nd(home_addr, ifindex, 
++                         flags & MIPV6_BU_F_LLADDR) != 0) {
++              DEBUG(DBG_ERROR, "mipv6_proxy_nd failed!");
++              mipv6_del_tnl_to_mn(coa, our_addr, home_addr);
++              return -2;
++      }
++      return 0;
++}
++
++static void ha_proxy_del(struct in6_addr *home_addr, struct mipv6_bce *entry)
++{
++      if (mipv6_proxy_nd_rem(&entry->home_addr, entry->ifindex,
++                             entry->flags & MIPV6_BU_F_LLADDR) == 0) {
++              DEBUG(DBG_INFO, "proxy_nd succ");
++      } else {
++              DEBUG(DBG_INFO, "proxy_nd fail");
++      }
++      mipv6_del_tnl_to_mn(&entry->coa, &entry->our_addr, home_addr);
++}
++
++static void bc_home_add(int ifindex, 
++                      struct in6_addr *daddr, struct in6_addr *haddr, 
++                      struct in6_addr *coa, struct in6_addr *rep_coa,
++                      __u32 lifetime, __u16 sequence, __u8 flags, 
++                      __u8 *k_bu)
++{
++      struct inet6_ifaddr *ifp = NULL;
++      __u8 ba_status = SUCCESS;
++
++      DEBUG_FUNC();
++
++      ifp = is_on_link_ipv6_address(haddr, daddr);
++
++      if (ifp == NULL) {
++              ba_status = NOT_HOME_SUBNET;
++      } else if (((ipv6_addr_type(haddr) & IPV6_ADDR_SITELOCAL) ||
++                  (ipv6_addr_type(coa) & IPV6_ADDR_SITELOCAL))
++                 && !mipv6_ha_tunnel_sitelocal) {
++              /* Site-local home or care-of addresses are not 
++                 accepted by default */
++              ba_status = ADMINISTRATIVELY_PROHIBITED;
++      } else {
++              int ret;
++
++              ifindex = ifp->idev->dev->ifindex;
++
++              if ((ret = mipv6_dad_start(ifp, ifindex, daddr, 
++                                         haddr, coa, rep_coa, lifetime,
++                                         sequence, flags)) < 0) {
++                      /* An error occurred */
++                      ba_status = -ret;
++              } else if (ret) {
++                      /* DAD is needed to be performed. */
++                      in6_ifa_put(ifp);
++                      return;
++              }
++      }
++
++      mipv6_bu_finish(ifp, ifindex, ba_status, daddr, haddr, coa, 
++                      rep_coa, lifetime, sequence, flags, k_bu);
++      if (ifp)
++              in6_ifa_put(ifp);
++}
++
++static void bc_home_delete(struct in6_addr *daddr, struct in6_addr *haddr, 
++                         struct in6_addr *coa, struct in6_addr *rep_coa, 
++                         __u16 sequence, __u8 flags, __u8 *k_bu)
++{
++      __u8 status = SUCCESS;
++      struct mipv6_bce bce;
++
++      /* Primary Care-of Address Deregistration */
++      if (mipv6_bcache_get(haddr, daddr, &bce) < 0) {
++              DEBUG(DBG_INFO, "entry is not in cache");
++              status = NOT_HA_FOR_MN;
++      } else {
++              ha_proxy_del(&bce.home_addr, &bce);
++              mipv6_bcache_delete(haddr, daddr, HOME_REGISTRATION);
++      }
++      mipv6_send_ba(daddr, haddr, coa, rep_coa, status, sequence, 0, k_bu);
++}
++
++extern int mipv6_ra_rcv_ptr(struct sk_buff *skb, struct icmp6hdr *msg);
++
++
++static int
++mipv6_ha_tnl_xmit_stats_hook(struct ip6_tnl *t, struct sk_buff *skb)
++{
++      DEBUG_FUNC();
++      if (is_mip6_tnl(t))
++              MIPV6_INC_STATS(n_encapsulations);
++      return IP6_TNL_ACCEPT;
++}
++
++static struct ip6_tnl_hook_ops mipv6_ha_tnl_xmit_stats_ops = {
++      {NULL, NULL},
++      IP6_TNL_PRE_ENCAP,
++      IP6_TNL_PRI_LAST,
++      mipv6_ha_tnl_xmit_stats_hook
++};
++
++static int
++mipv6_ha_tnl_rcv_stats_hook(struct ip6_tnl *t, struct sk_buff *skb)
++{
++      DEBUG_FUNC();
++      if (is_mip6_tnl(t))
++              MIPV6_INC_STATS(n_decapsulations);
++      return IP6_TNL_ACCEPT;
++}
++
++static struct ip6_tnl_hook_ops mipv6_ha_tnl_rcv_stats_ops = {
++      {NULL, NULL},
++      IP6_TNL_PRE_DECAP,
++      IP6_TNL_PRI_LAST,
++      mipv6_ha_tnl_rcv_stats_hook
++};
++
++static struct mip6_func old;
++
++int __init mipv6_ha_init(void)
++{
++      DEBUG_FUNC();
++      
++#ifdef CONFIG_SYSCTL
++      if (!(mipv6_ha_sysctl_header = 
++            register_sysctl_table(mipv6_ha_sysctl.mipv6_root_table, 0)))
++              printk(KERN_ERR "Failed to register sysctl handlers!");
++#endif
++      memcpy(&old, &mip6_fn, sizeof(struct mip6_func));
++      mip6_fn.bce_home_add = bc_home_add;
++      mip6_fn.bce_home_del = bc_home_delete;
++      mip6_fn.proxy_del = ha_proxy_del;
++      mip6_fn.proxy_create = ha_proxy_create;
++      /*  register packet interception hooks  */
++      ip6ip6_tnl_register_hook(&mipv6_ha_tnl_xmit_stats_ops);
++      ip6ip6_tnl_register_hook(&mipv6_ha_tnl_rcv_stats_ops);
++      return 0;
++}
++
++void __exit mipv6_ha_exit(void)
++{
++      DEBUG_FUNC();
++
++#ifdef CONFIG_SYSCTL
++      unregister_sysctl_table(mipv6_ha_sysctl_header);
++#endif
++
++      /*  remove packet interception hooks  */
++      ip6ip6_tnl_unregister_hook(&mipv6_ha_tnl_rcv_stats_ops);
++      ip6ip6_tnl_unregister_hook(&mipv6_ha_tnl_xmit_stats_ops);
++
++      mip6_fn.bce_home_add = old.bce_home_add;
++      mip6_fn.bce_home_del = old.bce_home_del;
++      mip6_fn.proxy_del = old.proxy_del;
++      mip6_fn.proxy_create = old.proxy_create;
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/ha.h linux-2.4.25/net/ipv6/mobile_ip6/ha.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/ha.h  1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/ha.h      2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,39 @@
++/*
++ *      MIPL Mobile IPv6 Home Agent header file
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _HA_H
++#define _HA_H
++
++int mipv6_ha_init(void);
++void mipv6_ha_exit(void);
++
++int mipv6_dad_start(struct inet6_ifaddr *ifp, int ifindex,
++                  struct in6_addr *daddr, struct in6_addr *haddr,
++                  struct in6_addr *coa, struct in6_addr *rep_coa,
++                  __u32 ba_lifetime, __u16 sequence, __u8 flags);
++
++void mipv6_bu_finish(struct inet6_ifaddr *ifp, int ifindex, 
++                   __u8 ba_status, struct in6_addr *daddr,
++                   struct in6_addr *haddr, struct in6_addr *coa, 
++                   struct in6_addr *rep_coa, __u32 ba_lifetime,
++                   __u16 sequence, __u8 flags, __u8 *k_bu);
++
++
++static __inline__ void mipv6_generate_ll_addr(struct in6_addr *ll_addr,
++                                            struct in6_addr *addr)
++{
++      ll_addr->s6_addr32[0] = htonl(0xfe800000);
++      ll_addr->s6_addr32[1] = 0;
++      ll_addr->s6_addr32[2] = addr->s6_addr32[2];
++      ll_addr->s6_addr32[3] = addr->s6_addr32[3];
++}
++
++#endif
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/halist.c linux-2.4.25/net/ipv6/mobile_ip6/halist.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/halist.c      1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/halist.c  2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,507 @@
++/*
++ *      Home Agents List
++ *
++ *      Authors:
++ *      Antti Tuominen          <ajtuomin@tml.hut.fi>
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ *
++ */
++
++#define PREF_BASE 0xffff /* MAX value for u16 field in RA */
++
++#include <linux/autoconf.h>
++#include <linux/sched.h>
++#include <linux/timer.h>
++#include <linux/proc_fs.h>
++#include <linux/init.h>
++#include <net/ipv6.h>
++#include <net/addrconf.h>
++
++#include "hashlist.h"
++#include "util.h"
++#include "debug.h"
++
++struct mipv6_halist {
++      struct hashlist *entries;
++      struct timer_list expire_timer;
++};
++
++static rwlock_t home_agents_lock = RW_LOCK_UNLOCKED;
++
++static struct mipv6_halist home_agents;
++
++struct mipv6_halist_entry {
++      struct hashlist_entry e;
++      int ifindex;                     /* Link identifier             */
++      struct in6_addr link_local_addr; /* HA's link-local address     */
++      struct in6_addr global_addr;     /* HA's Global address         */
++      int plen;
++      long preference;                 /* The preference for this HA  */
++      unsigned long expire;            /* expiration time (jiffies)   */
++};
++
++static inline void mipv6_ha_ac_add(struct in6_addr *ll_addr, int ifindex,
++                                 struct in6_addr *glob_addr, int plen)
++{
++      struct net_device *dev;
++
++      if ((dev = __dev_get_by_index(ifindex)) && ipv6_chk_addr(ll_addr, dev)) {
++              struct in6_addr addr;
++              mipv6_ha_anycast(&addr, glob_addr, plen);
++              ipv6_dev_ac_inc(dev, &addr);
++      }
++}
++
++static inline void mipv6_ha_ac_del(struct in6_addr *ll_addr, int ifindex,
++                                 struct in6_addr *glob_addr, int plen)
++{
++      struct net_device *dev;
++
++      if ((dev = __dev_get_by_index(ifindex)) && ipv6_chk_addr(ll_addr, dev)) {
++              struct in6_addr addr;
++              mipv6_ha_anycast(&addr, glob_addr, plen);
++              ipv6_dev_ac_dec(dev, &addr);
++      }
++}
++
++struct preflist_iterator_args {
++      int count;
++      int requested;
++      int ifindex;
++      struct in6_addr *list;
++};
++
++static int preflist_iterator(void *data, void *args,
++                           unsigned long *pref)
++{
++      struct preflist_iterator_args *state =
++              (struct preflist_iterator_args *)args;
++      struct mipv6_halist_entry *entry =
++              (struct mipv6_halist_entry *)data;
++      struct in6_addr *newaddr =
++              (struct in6_addr *)state->list + state->count;
++
++      if (state->count >= state->requested)
++              return ITERATOR_STOP;
++
++      if (time_after(jiffies, entry->expire)) {
++              if (!ipv6_addr_any(&entry->link_local_addr)) {
++                      mipv6_ha_ac_del(&entry->link_local_addr, 
++                                      entry->ifindex, 
++                                      &entry->global_addr, entry->plen);
++              }
++              DEBUG(DBG_INFO, "preflist_iterator: Deleting entry with address %x:%x:%x:%x:%x:%x:%x:%x to list", NIPV6ADDR(&entry->global_addr));
++              return ITERATOR_DELETE_ENTRY;
++      }
++      if (state->ifindex != entry->ifindex)
++              return ITERATOR_CONT;
++
++      ipv6_addr_copy(newaddr, &entry->global_addr);
++      DEBUG(DBG_INFO, "preflist_iterator: adding new entry with address %x:%x:%x:%x:%x:%x:%x:%x to list", NIPV6ADDR(&entry->global_addr));
++      state->count++;
++
++      return ITERATOR_CONT;
++}
++
++static int gc_iterator(void *data, void *args,
++                     unsigned long *pref)
++{
++      struct mipv6_halist_entry *entry =
++              (struct mipv6_halist_entry *)data;
++
++      int *type = (int *)args;
++
++      if (*type == 1 || time_after(jiffies, entry->expire)) {
++              if (!ipv6_addr_any(&entry->link_local_addr)) {
++                      mipv6_ha_ac_del(&entry->link_local_addr, 
++                                      entry->ifindex, 
++                                      &entry->global_addr, entry->plen);
++              }
++              return ITERATOR_DELETE_ENTRY;
++      }
++
++      return ITERATOR_CONT;
++}
++
++static int mipv6_halist_gc(int type)
++{
++      DEBUG_FUNC();
++      hashlist_iterate(home_agents.entries, &type, gc_iterator);
++      return 0;
++}
++
++static void mipv6_halist_expire(unsigned long dummy)
++{
++      DEBUG_FUNC();
++
++      write_lock(&home_agents_lock);
++      mipv6_halist_gc(0);
++      write_unlock(&home_agents_lock);
++}
++
++
++static struct mipv6_halist_entry *mipv6_halist_new_entry(void)
++{
++      struct mipv6_halist_entry *entry;
++
++      DEBUG_FUNC();
++
++      entry = hashlist_alloc(home_agents.entries, SLAB_ATOMIC);
++
++      return entry;
++}
++
++
++
++/**
++ * mipv6_halist_add - Add new home agent to the Home Agents List
++ * @ifindex: interface identifier
++ * @glob_addr: home agent's global address
++ * @ll_addr: home agent's link-local address
++ * @pref: relative preference for this home agent
++ * @lifetime: lifetime for the entry
++ *
++ * Adds new home agent to the Home Agents List.  The list is interface
++ * specific and @ifindex tells through which interface the home agent
++ * was heard.  Returns zero on success and negative on failure.
++ **/
++
++int mipv6_halist_add(int ifindex, struct in6_addr *glob_addr, int plen,
++                   struct in6_addr *ll_addr, unsigned int pref, __u32 lifetime)
++{
++      int update = 0, ret = 0;
++      unsigned int mpref;
++      struct mipv6_halist_entry *entry = NULL;
++
++      DEBUG_FUNC();
++
++      write_lock(&home_agents_lock);
++
++      if (glob_addr == NULL || lifetime <= 0) {
++              DEBUG(DBG_WARNING, "invalid arguments");
++              ret = -EINVAL;
++              goto out;
++      }
++      mpref = PREF_BASE - pref;
++      if ((entry = (struct mipv6_halist_entry *)
++           hashlist_get(home_agents.entries, glob_addr)) != NULL) {
++              if (entry->ifindex == ifindex) {
++                      DEBUG(DBG_DATADUMP, "updating old entry with address %x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(glob_addr));
++                      update = 1;
++              } else {
++                      DEBUG(DBG_INFO, "halist_add : adding new entry with address %x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(glob_addr));
++                      update = 0;
++              }
++      }
++      if (update) {
++              entry->expire = jiffies + lifetime * HZ;
++              if (entry->preference != mpref) {
++                      entry->preference = mpref;
++                      ret = hashlist_reposition(home_agents.entries, 
++                                                (void *)entry, mpref);
++              }
++      } else {
++              entry = mipv6_halist_new_entry();
++              if (entry == NULL) {
++                      DEBUG(DBG_INFO, "list full");
++                      ret = -ENOMEM;
++                      goto out;
++              }
++              entry->ifindex = ifindex;
++              if (ll_addr) {
++                      ipv6_addr_copy(&entry->link_local_addr, ll_addr);
++                      mipv6_ha_ac_add(ll_addr, ifindex, glob_addr, plen);
++              } else
++                      ipv6_addr_set(&entry->link_local_addr, 0, 0, 0, 0);
++
++              ipv6_addr_copy(&entry->global_addr, glob_addr);
++              entry->plen = plen;
++              entry->preference = mpref;
++              entry->expire = jiffies + lifetime * HZ;
++              ret = hashlist_add(home_agents.entries, glob_addr, mpref, 
++                                 entry);
++      }
++out:
++      write_unlock(&home_agents_lock);
++      return ret;
++}
++
++/**
++ * mipv6_halist_delete - delete home agent from Home Agents List
++ * @glob_addr: home agent's global address
++ *
++ * Deletes entry for home agent @glob_addr from the Home Agent List.
++ **/
++int mipv6_halist_delete(struct in6_addr *glob_addr)
++{
++      struct hashlist_entry *e;
++      struct mipv6_halist_entry *entry;
++      DEBUG_FUNC();
++
++      if (glob_addr == NULL) {
++              DEBUG(DBG_WARNING, "invalid glob addr");
++              return -EINVAL;
++      }
++      write_lock(&home_agents_lock);
++      if ((e = hashlist_get(home_agents.entries, glob_addr)) == NULL) {
++              write_unlock(&home_agents_lock);
++              return -ENOENT;
++      }
++      hashlist_delete(home_agents.entries, e);
++      entry = (struct mipv6_halist_entry *)e;
++      if (!ipv6_addr_any(&entry->link_local_addr)) {
++              mipv6_ha_ac_del(&entry->link_local_addr, entry->ifindex, 
++                              &entry->global_addr, entry->plen);
++      }
++      hashlist_free(home_agents.entries, e);
++      write_unlock(&home_agents_lock);
++      return 0;
++}
++
++/**
++ * mipv6_ha_get_pref_list - Get list of preferred home agents
++ * @ifindex: interface identifier
++ * @addrs: pointer to a buffer to store the list
++ * @max: maximum number of home agents to return
++ *
++ * Creates a list of @max preferred (or all known if less than @max)
++ * home agents.  Home Agents List is interface specific so you must
++ * supply @ifindex.  Stores list in addrs and returns number of home
++ * agents stored.  On failure, returns a negative value.
++ **/
++int mipv6_ha_get_pref_list(int ifindex, struct in6_addr **addrs, int max)
++{
++      struct preflist_iterator_args args;
++
++      DEBUG_FUNC();
++      if (max <= 0) {
++              *addrs = NULL;
++              return 0;
++      }
++
++      args.count = 0;
++      args.requested = max;
++      args.ifindex = ifindex;
++      args.list = kmalloc(max * sizeof(struct in6_addr), GFP_ATOMIC);
++
++      if (args.list == NULL) return -ENOMEM;
++
++      read_lock(&home_agents_lock);
++      hashlist_iterate(home_agents.entries, &args, preflist_iterator);
++      read_unlock(&home_agents_lock);
++
++      if (args.count >= 0) {
++              *addrs = args.list;
++      } else {
++              kfree(args.list);
++              *addrs = NULL;
++      }
++
++      return args.count;
++}
++
++struct getaddr_iterator_args {
++      struct net_device *dev;
++      struct in6_addr *addr;
++};
++
++static int getaddr_iterator(void *data, void *args,
++           unsigned long *pref)
++{
++      struct mipv6_halist_entry *entry =
++              (struct mipv6_halist_entry *)data;
++      struct getaddr_iterator_args *state =
++              (struct getaddr_iterator_args *)args;
++
++      if (entry->ifindex != state->dev->ifindex)
++              return ITERATOR_CONT;
++
++      if (ipv6_chk_addr(&entry->global_addr, state->dev)) {
++              ipv6_addr_copy(state->addr, &entry->global_addr);
++              return ITERATOR_STOP;
++      }
++      return ITERATOR_CONT;
++}
++
++/*
++ * Get Home Agent Address for given interface.  If node is not serving
++ * as a HA for this interface returns negative error value.
++ */
++int mipv6_ha_get_addr(int ifindex, struct in6_addr *addr)
++{
++      struct getaddr_iterator_args args;
++      struct net_device *dev;
++
++      if (ifindex <= 0)
++              return -EINVAL;
++
++      if ((dev = dev_get_by_index(ifindex)) == NULL)
++              return -ENODEV;
++
++      memset(addr, 0, sizeof(struct in6_addr));
++      args.dev = dev;
++      args.addr = addr;
++      read_lock(&home_agents_lock);
++      hashlist_iterate(home_agents.entries, &args, getaddr_iterator);
++      read_unlock(&home_agents_lock);
++      dev_put(dev);
++
++      if (ipv6_addr_any(addr))
++              return -ENOENT;
++      
++      return 0;
++}
++
++#define HALIST_INFO_LEN 81
++
++struct procinfo_iterator_args {
++      char *buffer;
++      int offset;
++      int length;
++      int skip;
++      int len;
++};
++
++static int procinfo_iterator(void *data, void *args,
++                           unsigned long *pref)
++{
++      struct procinfo_iterator_args *arg =
++              (struct procinfo_iterator_args *)args;
++      struct mipv6_halist_entry *entry =
++              (struct mipv6_halist_entry *)data;
++      unsigned long int expire;
++
++      DEBUG_FUNC();
++
++      if (entry == NULL) return ITERATOR_ERR;
++
++      if (time_after(jiffies, entry->expire)) {
++              if (!ipv6_addr_any(&entry->link_local_addr)) {
++                      mipv6_ha_ac_del(&entry->link_local_addr, 
++                                      entry->ifindex, 
++                                      &entry->global_addr, entry->plen);
++              }
++              return ITERATOR_DELETE_ENTRY;
++      }
++      if (arg->skip < arg->offset / HALIST_INFO_LEN) {
++              arg->skip++;
++              return ITERATOR_CONT;
++      }
++
++      if (arg->len >= arg->length)
++              return ITERATOR_CONT;
++
++      expire = (entry->expire - jiffies) / HZ;
++
++      arg->len += sprintf(arg->buffer + arg->len, 
++                          "%02d %08x%08x%08x%08x %08x%08x%08x%08x %05ld %05ld\n",
++                          entry->ifindex,
++                          ntohl(entry->global_addr.s6_addr32[0]),
++                          ntohl(entry->global_addr.s6_addr32[1]),
++                          ntohl(entry->global_addr.s6_addr32[2]),
++                          ntohl(entry->global_addr.s6_addr32[3]),
++                          ntohl(entry->link_local_addr.s6_addr32[0]),
++                          ntohl(entry->link_local_addr.s6_addr32[1]),
++                          ntohl(entry->link_local_addr.s6_addr32[2]),
++                          ntohl(entry->link_local_addr.s6_addr32[3]),
++                          -(entry->preference - PREF_BASE), expire);
++
++      return ITERATOR_CONT;
++}
++
++static int halist_proc_info(char *buffer, char **start, off_t offset,
++                            int length)
++{
++      struct procinfo_iterator_args args;
++
++      DEBUG_FUNC();
++
++      args.buffer = buffer;
++      args.offset = offset;
++      args.length = length;
++      args.skip = 0;
++      args.len = 0;
++
++      read_lock_bh(&home_agents_lock);
++      hashlist_iterate(home_agents.entries, &args, procinfo_iterator);
++      read_unlock_bh(&home_agents_lock);
++
++      *start = buffer;
++      if (offset)
++              *start += offset % HALIST_INFO_LEN;
++
++      args.len -= offset % HALIST_INFO_LEN;
++
++      if (args.len > length)
++              args.len = length;
++      if (args.len < 0)
++              args.len = 0;
++      
++      return args.len;
++}
++
++static int halist_compare(void *data, void *hashkey)
++{
++      struct mipv6_halist_entry *e = (struct mipv6_halist_entry *)data;
++      struct in6_addr *key = (struct in6_addr *)hashkey;
++
++      return ipv6_addr_cmp(&e->global_addr, key);
++}
++
++static __u32 halist_hash(void *hashkey)
++{
++      struct in6_addr *key = (struct in6_addr *)hashkey;
++      __u32 hash;
++
++      hash = key->s6_addr32[0] ^
++                key->s6_addr32[1] ^
++                key->s6_addr32[2] ^
++                key->s6_addr32[3];
++
++      return hash;
++}
++
++int __init mipv6_halist_init(__u32 size)
++{
++      DEBUG_FUNC();
++
++      if (size <= 0) {
++              DEBUG(DBG_ERROR, "size must be at least 1");
++              return -EINVAL;
++      }
++      init_timer(&home_agents.expire_timer);
++      home_agents.expire_timer.data = 0;
++      home_agents.expire_timer.function = mipv6_halist_expire;
++      home_agents_lock = RW_LOCK_UNLOCKED;
++
++      home_agents.entries = hashlist_create(16, size, sizeof(struct mipv6_halist_entry),
++                                             "mip6_halist", NULL, NULL,
++                                             halist_compare, halist_hash);
++
++      if (home_agents.entries == NULL) {
++              DEBUG(DBG_ERROR, "Failed to initialize hashlist");
++              return -ENOMEM;
++      }
++
++      proc_net_create("mip6_home_agents", 0, halist_proc_info);
++      DEBUG(DBG_INFO, "Home Agents List initialized");
++      return 0;
++}
++
++void __exit mipv6_halist_exit(void)
++{
++      DEBUG_FUNC();
++      proc_net_remove("mip6_home_agents");
++      write_lock_bh(&home_agents_lock);
++      DEBUG(DBG_INFO, "Stopping the halist timer");
++      del_timer(&home_agents.expire_timer);
++      mipv6_halist_gc(1);
++      write_unlock_bh(&home_agents_lock);
++      hashlist_destroy(home_agents.entries);
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/halist.h linux-2.4.25/net/ipv6/mobile_ip6/halist.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/halist.h      1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/halist.h  2004-06-26 11:29:30.000000000 +0100
+@@ -0,0 +1,28 @@
++/*
++ *      MIPL Mobile IPv6 Home Agents List header file      
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _HALIST_H
++#define _HALIST_H
++
++int mipv6_halist_init(__u32 size);
++
++void mipv6_halist_exit(void);
++
++int mipv6_halist_add(int ifindex, struct in6_addr *glob_addr, int plen,
++                   struct in6_addr *ll_addr, unsigned int pref, __u32 lifetime);
++
++int mipv6_halist_delete(struct in6_addr *glob_addr);
++
++int mipv6_ha_get_pref_list(int ifindex, struct in6_addr **addrs, int max);
++
++int mipv6_ha_get_addr(int ifindex, struct in6_addr *addr);
++
++#endif /* _HALIST_H */
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/hashlist.c linux-2.4.25/net/ipv6/mobile_ip6/hashlist.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/hashlist.c    1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/hashlist.c        2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,351 @@
++/*
++ *    Generic hashtable with chaining.  Supports secodary sort order
++ *    with doubly linked-list.
++ *
++ *    Authors:
++ *    Sami Kivisaari          <skivisaa@cc.hut.fi>
++ *    Antti Tuominen          <ajtuomin@tml.hut.fi>
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ */
++
++#include <linux/slab.h>
++#include "hashlist.h"
++#include "debug.h"
++
++struct hashlist {
++      int count;              /* entry count  */
++      int maxcount;           /* max entries  */
++      __u32 bucketnum;        /* hash buckets */
++
++      kmem_cache_t *kmem;
++
++      struct list_head *hashtable;
++      struct list_head sortedlist;
++
++      int (*compare)(void *data, void *hashkey);
++      __u32 (*hash_function)(void *hashkey);
++};
++
++/**
++ * hashlist_create - Create new hashlist
++ * @bucketnum: number of hash buckets
++ * @maxentries: maximum number of entries (0 = no limit)
++ * @size: entry size in bytes
++ * @name: name for kmem_cache_t
++ * @ctor: kmem_cache_t constructor
++ * @dtor: kmem_cache_t destructor
++ * @compare: compare function for key
++ * @hash_function: hash function
++ *
++ * Creates a hashlist structure with @max_entries entries of @size
++ * bytes.  User must supply @hash_function and @compare function for
++ * the hashlist.  User can also supply @ctor and @dtor for kmem_cache.
++ **/
++struct hashlist *hashlist_create(int bucketnum, int max_entries, size_t size,
++                               char *name,
++                               void (*ctor)(void *, kmem_cache_t *, unsigned long),
++                               void (*dtor)(void *, kmem_cache_t *, unsigned long),
++                               int (*compare)(void *data, void *hashkey),
++                               __u32 (*hash_function)(void *hashkey))
++{
++      int i;
++      struct hashlist *hl;
++
++      if (!compare || !hash_function)
++              goto hlfailed;
++
++      hl = kmalloc(sizeof(struct hashlist), GFP_ATOMIC);
++      if (!hl) goto hlfailed;
++
++      hl->kmem = kmem_cache_create(name, size, 0, 0, ctor, dtor);
++      if (!hl->kmem) goto poolfailed;
++
++      hl->hashtable = kmalloc(
++              sizeof(struct list_head) * bucketnum, GFP_ATOMIC);
++      if (!hl->hashtable) goto hashfailed;
++
++      for (i = 0; i < bucketnum; i++)
++              INIT_LIST_HEAD(&hl->hashtable[i]);
++
++      INIT_LIST_HEAD(&hl->sortedlist);
++
++      hl->maxcount = max_entries;
++      hl->count = 0;
++      hl->bucketnum = bucketnum;
++      hl->compare = compare;
++      hl->hash_function = hash_function;
++
++      return hl;
++
++hashfailed:
++      kmem_cache_destroy(hl->kmem);
++      hl->kmem = NULL;
++
++poolfailed:
++      kfree(hl);
++
++hlfailed:
++      DEBUG(DBG_ERROR, "could not create hashlist");
++
++      return NULL;    
++}
++
++/**
++ * hashlist_destroy - Destroy hashlist
++ * @hashlist: hashlist to destroy
++ *
++ * Frees all memory allocated for a hashlist.
++ **/
++void hashlist_destroy(struct hashlist *hashlist)
++{
++      DEBUG_FUNC();
++
++      if (hashlist == NULL) return;
++
++      if (hashlist->hashtable) {
++              kfree(hashlist->hashtable);
++              hashlist->hashtable = NULL;
++      }
++
++      if (hashlist->kmem) {
++              kmem_cache_destroy(hashlist->kmem);
++              hashlist->kmem = NULL;
++      }
++
++      kfree(hashlist);
++
++      return;
++}
++
++/*
++ * Insert a chain of entries to hashlist into correct order.  The
++ * entries are assumed to have valid hashkeys.  We use time_after_eq
++ * for comparing, since it handles wrap-around correctly, and the
++ * sortkey is usually jiffies.
++ */
++static void sorted_insert(struct list_head *lh, struct hashlist_entry *he)
++{
++      struct list_head *p;
++      struct hashlist_entry *hlp = NULL;
++      unsigned long sortkey = he->sortkey;
++
++      if (list_empty(lh)) {
++              list_add(&he->sorted, lh);
++              return;
++      }
++      
++      list_for_each(p, lh) {
++              hlp = list_entry(p, typeof(*hlp), sorted);
++              if (time_after_eq(hlp->sortkey, sortkey)) {
++                      list_add(&he->sorted, hlp->sorted.prev);
++                      return;
++              }
++      }
++      list_add(&he->sorted, &hlp->sorted);
++}
++
++/**
++ * hashlist_iterate - Apply function for all elements in a hash list
++ * @hashlist: pointer to hashlist
++ * @args: data to pass to the function
++ * @func: pointer to a function
++ *
++ * Apply arbitrary function @func to all elements in a hash list.
++ * @func must be a pointer to a function with the following prototype:
++ * int func(void *entry, void *arg, struct in6_addr *hashkey, unsigned
++ * long *sortkey).  Function must return %ITERATOR_STOP,
++ * %ITERATOR_CONT or %ITERATOR_DELETE_ENTRY.  %ITERATOR_STOP stops
++ * iterator and returns last return value from the function.
++ * %ITERATOR_CONT continues with iteration.  %ITERATOR_DELETE_ENTRY
++ * deletes current entry from the hashlist.  If function changes
++ * hashlist element's sortkey, iterator automatically schedules
++ * element to be reinserted after all elements have been processed.
++ */
++int hashlist_iterate(
++      struct hashlist *hashlist, void *args,
++      hashlist_iterator_t func)
++{
++      int res = ITERATOR_CONT;
++      unsigned long skey;
++      struct list_head *p, *n, repos;
++      struct hashlist_entry *he;
++
++      DEBUG_FUNC();
++      INIT_LIST_HEAD(&repos);
++
++      list_for_each_safe(p, n, &hashlist->sortedlist) {
++              he = list_entry(p, typeof(*he), sorted);
++              if (res == ITERATOR_STOP)
++                      break;
++              skey = he->sortkey;
++              res = func(he, args, &he->sortkey);
++              if (res == ITERATOR_DELETE_ENTRY) {
++                      hashlist_delete(hashlist, he);
++                      hashlist_free(hashlist, he);
++              } else if (skey != he->sortkey) {
++                      /* iterator changed the sortkey, schedule for
++                       * repositioning */
++                      list_move(&he->sorted, &repos);
++              }
++      }
++      list_for_each_safe(p, n, &repos) {      
++              he = list_entry(p, typeof(*he), sorted);
++              sorted_insert(&hashlist->sortedlist, he);
++      }
++      return res;
++}
++
++/**
++ * hashlist_alloc - Allocate memory for a hashlist entry
++ * @hashlist: hashlist for allocated entry
++ * @size: size of entry in bytes
++ *
++ * Allocates @size bytes memory from @hashlist->kmem.
++ **/
++void *hashlist_alloc(struct hashlist *hashlist, int type)
++{
++      if (hashlist == NULL) return NULL;
++      return kmem_cache_alloc(hashlist->kmem, type);
++}
++
++/**
++ * hashlist_free - Free hashlist entry
++ * @hashlist: hashlist where @he is
++ * @he: entry to free
++ *
++ * Frees an allocated hashlist entry.
++ **/
++void hashlist_free(struct hashlist *hashlist, struct hashlist_entry *he)
++{
++      kmem_cache_free(hashlist->kmem, he);
++}
++
++/**
++ * hashlist_add - Add element to hashlist
++ * @hashlist: pointer to hashlist
++ * @hashkey: hashkey for the element
++ * @sortkey: key for sorting
++ * @data: element data
++ *
++ * Add element to hashlist.  Hashlist is also sorted in a linked list
++ * by @sortkey.
++ */
++int hashlist_add(struct hashlist *hashlist, void *hashkey,
++               unsigned long sortkey, void *entry)
++{
++      struct hashlist_entry *he = (struct hashlist_entry *)entry;
++      unsigned int hash;
++
++      if (hashlist->count >= hashlist->maxcount)
++              return -1;
++
++      hashlist->count++;
++
++      /*  link the entry to sorted order  */ 
++      he->sortkey = sortkey;
++      sorted_insert(&hashlist->sortedlist, he);
++
++      /*  hash the entry  */
++      hash = hashlist->hash_function(hashkey) % hashlist->bucketnum;
++      list_add(&he->hashlist, &hashlist->hashtable[hash]);
++
++      return 0;
++}
++
++/**
++ * hashlist_get_ex - Get element from hashlist
++ * @hashlist: hashlist
++ * @hashkey: hashkey of the desired entry
++ *
++ * Lookup entry with @hashkey from the hash table using @compare
++ * function for entry comparison.  Returns entry on success, otherwise
++ * %NULL.
++ **/
++struct hashlist_entry *hashlist_get_ex(
++      struct hashlist *hashlist, void *hashkey,
++      int (*compare)(void *data, void *hashkey))
++{
++      struct list_head *p, *bkt;
++      __u32 hash;
++
++      hash = hashlist->hash_function(hashkey) % hashlist->bucketnum;
++      bkt = &hashlist->hashtable[hash];
++
++      /*  scan the entries within the same hashbucket  */
++      list_for_each(p, bkt) {
++              struct hashlist_entry *he = list_entry(p, typeof(*he), 
++                                                     hashlist);
++              if (compare(he, hashkey) == 0)
++                      return he;
++      }
++
++      return NULL;
++}
++
++/**
++ * hashlist_get - Get element from hashlist
++ * @hashlist: hashlist
++ * @hashkey: hashkey of the desired entry
++ *
++ * Lookup entry with @hashkey from the hash table.  Returns entry on
++ * success, otherwise %NULL.
++ **/
++struct hashlist_entry *hashlist_get(struct hashlist *hashlist, void *hashkey)
++{
++      return hashlist_get_ex(hashlist, hashkey, hashlist->compare);
++}
++
++/**
++ * hashlist_reposition - set entry to new position in the list
++ * @hashlist: hashlist
++ * @he: entry to reposition
++ * @sortkey: new sortkey of the entry
++ *
++ * If secondary order sortkey changes, entry must be repositioned in
++ * the sorted list.
++ **/
++int hashlist_reposition(struct hashlist *hashlist, struct hashlist_entry *he,
++                      unsigned long sortkey)
++{
++      list_del(&he->sorted);
++      he->sortkey = sortkey;
++      sorted_insert(&hashlist->sortedlist, he);
++
++      return 0;
++}
++
++/**
++ * hashlist_delete - Delete entry from hashlist
++ * @hashlist: hashlist where entry is
++ * @he: entry to delete
++ *
++ * Deletes an entry from the hashlist and sorted list.
++ **/
++void hashlist_delete(struct hashlist *hashlist,
++                   struct hashlist_entry *he)
++{
++      list_del_init(&he->hashlist);
++      list_del_init(&he->sorted);
++
++      hashlist->count--;
++}
++
++/**
++ * hashlist_get_first - Get first item from sorted list
++ * @hashlist: pointer to hashlist
++ *
++ * Returns first item in the secondary sort order.
++ **/
++void * hashlist_get_first(struct hashlist *hashlist)
++{
++      if (list_empty(&hashlist->sortedlist))
++              return NULL;
++      
++      return list_entry(hashlist->sortedlist.next, struct hashlist_entry, sorted);
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/hashlist.h linux-2.4.25/net/ipv6/mobile_ip6/hashlist.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/hashlist.h    1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/hashlist.h        2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,63 @@
++/*
++ *    MIPL Mobile IPv6 Hashlist header file
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ */
++
++#ifndef _HASHLIST_H
++#define _HASHLIST_H
++
++#define ITERATOR_ERR -1
++#define ITERATOR_CONT 0
++#define ITERATOR_STOP 1
++#define ITERATOR_DELETE_ENTRY 2
++
++struct kmem_cache_t;
++
++struct hashlist_entry {
++      unsigned long sortkey;
++      struct list_head sorted;
++      struct list_head hashlist;
++};
++
++struct hashlist * hashlist_create(
++      int bucketnum, int max_entries, size_t size, char *name,
++      void (*ctor)(void *, kmem_cache_t *, unsigned long),
++      void (*dtor)(void *, kmem_cache_t *, unsigned long),
++      int (*compare)(void *data, void *hashkey),
++      __u32 (*hash_function)(void *hashkey));
++
++void hashlist_destroy(struct hashlist *hashlist);
++
++void *hashlist_alloc(struct hashlist *hashlist, int type);
++
++void hashlist_free(struct hashlist *hashlist, struct hashlist_entry *he);
++
++struct hashlist_entry *hashlist_get(struct hashlist *hashlist, void *hashkey);
++
++struct hashlist_entry *hashlist_get_ex(
++      struct hashlist *hashlist, void *hashkey,
++      int (*compare)(void *data, void *hashkey));
++
++int hashlist_add(struct hashlist *hashlist, void *hashkey,
++               unsigned long sortkey, void *data);
++
++void hashlist_delete(struct hashlist *hashlist, struct hashlist_entry *he);
++
++/* iterator function */
++typedef int (*hashlist_iterator_t)(void *, void *, unsigned long *);
++
++int hashlist_iterate(struct hashlist *hashlist, void *args,
++                   hashlist_iterator_t func);
++
++void * hashlist_get_first(struct hashlist *hashlist);
++
++int hashlist_reposition(struct hashlist *hashlist, struct hashlist_entry *he,
++                      unsigned long sortkey);
++
++#endif
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/hmac.c linux-2.4.25/net/ipv6/mobile_ip6/hmac.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/hmac.c        1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/hmac.c    2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,658 @@
++/*     Authentication algorithms       
++ *    
++ *      Authors: 
++ *       Alexis Olivereau              <Alexis.Olivereau@crm.mot.com>
++ * 
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ *
++ *      Changes: 
++ *      Henrik Petander     :     Cleaned up unused parts
++ *
++ */
++
++#include <linux/sched.h>
++#include <linux/tty.h>
++#include <linux/types.h>
++#include <linux/slab.h>
++#include <linux/in6.h>
++
++#include "hmac.h"
++#define LROLL(x, s) (((x) << (s)) | ((x) >> (32 - (s))))
++
++/* MD5 */
++#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
++#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
++#define H(x, y, z) ((x) ^ (y) ^ (z))
++#define I(x, y, z) ((y) ^ ((x) | ~(z)))
++
++#define FF(a, b, c, d, m, s, t) { \
++ (a) += F ((b), (c), (d)) + (m) + (t); \
++ (a) = LROLL((a), (s)); \
++ (a) += (b); \
++ }
++#define GG(a, b, c, d, m, s, t) { \
++ (a) += G ((b), (c), (d)) + (m) + (t); \
++ (a) = LROLL((a), (s)); \
++ (a) += (b); \
++ }
++#define HH(a, b, c, d, m, s, t) { \
++ (a) += H ((b), (c), (d)) + (m) + (t); \
++ (a) = LROLL((a), (s)); \
++ (a) += (b); \
++ }
++#define II(a, b, c, d, m, s, t) { \
++ (a) += I ((b), (c), (d)) + (m) + (t); \
++ (a) = LROLL((a), (s)); \
++ (a) += (b); \
++ }
++
++#define s11  7
++#define s12 12
++#define s13 17
++#define s14 22
++#define s21  5
++#define s22  9
++#define s23 14
++#define s24 20
++#define s31  4
++#define s32 11
++#define s33 16
++#define s34 23
++#define s41  6
++#define s42 10
++#define s43 15
++#define s44 21
++
++/* SHA-1 */
++#define f(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
++#define g(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
++#define h(x, y, z) ((x) ^ (y) ^ (z))
++
++#define K1 0x5a827999
++#define K2 0x6ed9eba1
++#define K3 0x8f1bbcdc
++#define K4 0xca62c1d6
++
++int ah_hmac_md5_init(struct ah_processing *ahp, u_int8_t *key, u_int32_t key_len)
++{
++      int i;
++      int key_up4;
++      uint32_t ipad = 0x36363636;
++      uint8_t extkey[64];
++
++      ahp->key_auth = key;
++      ahp->key_auth_len = key_len;
++      ahp->context = (void *) kmalloc(sizeof(MD5_CTX), GFP_ATOMIC);
++      if (ahp->context == NULL)
++              return -1;
++      md5_init((MD5_CTX *) ahp->context);
++      if ((64 * sizeof(uint8_t)) < ahp->key_auth_len) {
++              printk("buffer overflow!");
++              return -1;
++      }
++      memcpy(extkey, ahp->key_auth, ahp->key_auth_len);
++      if (ahp->key_auth_len % 4) {
++              memset(extkey + ahp->key_auth_len, 0,
++                     4 - (ahp->key_auth_len % 4));
++      }
++      key_up4 = ((ahp->key_auth_len + 0x3) & 0xFFFFFFFC) / 4;
++
++      for (i = 0; i < key_up4; i++)
++              ((uint32_t *) extkey)[i] = ((uint32_t *) extkey)[i] ^ ipad;
++      for (i = key_up4; i < 16; i++)
++              ((uint32_t *) extkey)[i] = ipad;
++
++      md5_compute((MD5_CTX *) ahp->context, extkey, 64);
++      return 0;
++}
++
++void ah_hmac_md5_loop(struct ah_processing *ahp, void *str, uint32_t len)
++{
++      md5_compute((MD5_CTX *) ahp->context, str, len);
++}
++
++void ah_hmac_md5_result(struct ah_processing *ahp, char *digest)
++{
++      uint8_t inner[HMAC_MD5_HASH_LEN];
++      int i;
++      int key_up4;
++      uint32_t opad = 0x5c5c5c5c;
++      uint8_t extkey[64];
++
++      md5_final((MD5_CTX *) ahp->context, inner);
++      md5_init((MD5_CTX *) ahp->context);
++
++      memcpy(extkey, ahp->key_auth, ahp->key_auth_len);
++      if (ahp->key_auth_len % 4) {
++              memset(extkey + ahp->key_auth_len, 0,
++                     4 - (ahp->key_auth_len % 4));
++      }
++      key_up4 = ((ahp->key_auth_len + 0x3) & 0xFFFFFFFC) / 4;
++
++      for (i = 0; i < key_up4; i++)
++              ((uint32_t *) extkey)[i] = ((uint32_t *) extkey)[i] ^ opad;
++      for (i = key_up4; i < 16; i++)
++              ((uint32_t *) extkey)[i] = opad;
++
++      md5_compute((MD5_CTX *) ahp->context, extkey, 64);
++      md5_compute((MD5_CTX *) ahp->context, inner, HMAC_MD5_HASH_LEN);
++
++      md5_final((MD5_CTX *) ahp->context, digest);
++
++      kfree(ahp->context);
++}
++
++int ah_hmac_sha1_init(struct ah_processing *ahp, u_int8_t *key, u_int32_t key_len)
++{
++      int i;
++      int key_up4;
++      uint32_t ipad = 0x36363636;
++      uint8_t extkey[64];
++
++      ahp->key_auth = key;
++      ahp->key_auth_len = key_len;
++
++      ahp->context = (void *) kmalloc(sizeof(SHA1_CTX), GFP_ATOMIC);
++      //if (ahp->context == NULL)
++      //      return -1;
++
++      sha1_init((SHA1_CTX *) ahp->context);
++
++      memcpy(extkey, ahp->key_auth, ahp->key_auth_len);
++      if (ahp->key_auth_len % 4) {
++              memset(extkey + ahp->key_auth_len, 0,
++                     4 - (ahp->key_auth_len % 4));
++      }
++      key_up4 = ((ahp->key_auth_len + 0x3) & 0xFFFFFFFC) / 4;
++
++      for (i = 0; i < key_up4; i++)
++              ((uint32_t *) extkey)[i] = ((uint32_t *) extkey)[i] ^ ipad;
++      for (i = key_up4; i < 16; i++)
++              ((uint32_t *) extkey)[i] = ipad;
++
++      sha1_compute((SHA1_CTX *) ahp->context, extkey, 64);
++      return 0;
++}
++
++void ah_hmac_sha1_loop(struct ah_processing *ahp, void *str, uint32_t len)
++{
++      if (!ahp)
++              return;
++      sha1_compute((SHA1_CTX *) ahp->context, str, len);
++}
++
++void ah_hmac_sha1_result(struct ah_processing *ahp, char *digest)
++{
++      uint8_t inner[HMAC_SHA1_HASH_LEN];
++      int i;
++      int key_up4;
++      uint32_t opad = 0x5c5c5c5c;
++      uint8_t extkey[64];
++
++      if (!ahp)
++              return;
++      sha1_final((SHA1_CTX *) ahp->context, inner);
++      sha1_init((SHA1_CTX *) ahp->context);
++
++      memcpy(extkey, ahp->key_auth, ahp->key_auth_len);
++      if (ahp->key_auth_len % 4) {
++              memset(extkey + ahp->key_auth_len, 0,
++                     4 - (ahp->key_auth_len % 4));
++      }
++      key_up4 = ((ahp->key_auth_len + 0x3) & 0xFFFFFFFC) / 4;
++
++      for (i = 0; i < key_up4; i++)
++              ((uint32_t *) extkey)[i] = ((uint32_t *) extkey)[i] ^ opad;
++      for (i = key_up4; i < 16; i++)
++              ((uint32_t *) extkey)[i] = opad;
++
++      sha1_compute((SHA1_CTX *) ahp->context, extkey, 64);
++      sha1_compute((SHA1_CTX *) ahp->context, inner,
++                   HMAC_SHA1_HASH_LEN);
++
++      sha1_final((SHA1_CTX *) ahp->context, digest);
++
++      kfree(ahp->context);
++}
++
++void md5_init(MD5_CTX * ctx)
++{
++      ctx->A = 0x67452301;
++      ctx->B = 0xefcdab89;
++      ctx->C = 0x98badcfe;
++      ctx->D = 0x10325476;
++      ctx->buf_cur = ctx->buf;
++      ctx->bitlen[0] = ctx->bitlen[1] = 0;
++      memset(ctx->buf, 0, 64);
++}
++
++void md5_over_block(MD5_CTX * ctx, uint8_t * data)
++{
++      uint32_t M[16];
++      uint32_t a = ctx->A;
++      uint32_t b = ctx->B;
++      uint32_t c = ctx->C;
++      uint32_t d = ctx->D;
++
++      create_M_blocks(M, data);
++
++      /* Round 1 */
++      FF(a, b, c, d, M[0], s11, 0xd76aa478);  /*  1 */
++      FF(d, a, b, c, M[1], s12, 0xe8c7b756);  /*  2 */
++      FF(c, d, a, b, M[2], s13, 0x242070db);  /*  3 */
++      FF(b, c, d, a, M[3], s14, 0xc1bdceee);  /*  4 */
++      FF(a, b, c, d, M[4], s11, 0xf57c0faf);  /*  5 */
++      FF(d, a, b, c, M[5], s12, 0x4787c62a);  /*  6 */
++      FF(c, d, a, b, M[6], s13, 0xa8304613);  /*  7 */
++      FF(b, c, d, a, M[7], s14, 0xfd469501);  /*  8 */
++      FF(a, b, c, d, M[8], s11, 0x698098d8);  /*  9 */
++      FF(d, a, b, c, M[9], s12, 0x8b44f7af);  /* 10 */
++      FF(c, d, a, b, M[10], s13, 0xffff5bb1); /* 11 */
++      FF(b, c, d, a, M[11], s14, 0x895cd7be); /* 12 */
++      FF(a, b, c, d, M[12], s11, 0x6b901122); /* 13 */
++      FF(d, a, b, c, M[13], s12, 0xfd987193); /* 14 */
++      FF(c, d, a, b, M[14], s13, 0xa679438e); /* 15 */
++      FF(b, c, d, a, M[15], s14, 0x49b40821); /* 16 */
++
++      /* Round 2 */
++      GG(a, b, c, d, M[1], s21, 0xf61e2562);  /* 17 */
++      GG(d, a, b, c, M[6], s22, 0xc040b340);  /* 18 */
++      GG(c, d, a, b, M[11], s23, 0x265e5a51); /* 19 */
++      GG(b, c, d, a, M[0], s24, 0xe9b6c7aa);  /* 20 */
++      GG(a, b, c, d, M[5], s21, 0xd62f105d);  /* 21 */
++      GG(d, a, b, c, M[10], s22, 0x02441453); /* 22 */
++      GG(c, d, a, b, M[15], s23, 0xd8a1e681); /* 23 */
++      GG(b, c, d, a, M[4], s24, 0xe7d3fbc8);  /* 24 */
++      GG(a, b, c, d, M[9], s21, 0x21e1cde6);  /* 25 */
++      GG(d, a, b, c, M[14], s22, 0xc33707d6); /* 26 */
++      GG(c, d, a, b, M[3], s23, 0xf4d50d87);  /* 27 */
++      GG(b, c, d, a, M[8], s24, 0x455a14ed);  /* 28 */
++      GG(a, b, c, d, M[13], s21, 0xa9e3e905); /* 29 */
++      GG(d, a, b, c, M[2], s22, 0xfcefa3f8);  /* 30 */
++      GG(c, d, a, b, M[7], s23, 0x676f02d9);  /* 31 */
++      GG(b, c, d, a, M[12], s24, 0x8d2a4c8a); /* 32 */
++
++      /* Round 3 */
++      HH(a, b, c, d, M[5], s31, 0xfffa3942);  /* 33 */
++      HH(d, a, b, c, M[8], s32, 0x8771f681);  /* 34 */
++      HH(c, d, a, b, M[11], s33, 0x6d9d6122); /* 35 */
++      HH(b, c, d, a, M[14], s34, 0xfde5380c); /* 36 */
++      HH(a, b, c, d, M[1], s31, 0xa4beea44);  /* 37 */
++      HH(d, a, b, c, M[4], s32, 0x4bdecfa9);  /* 38 */
++      HH(c, d, a, b, M[7], s33, 0xf6bb4b60);  /* 39 */
++      HH(b, c, d, a, M[10], s34, 0xbebfbc70); /* 40 */
++      HH(a, b, c, d, M[13], s31, 0x289b7ec6); /* 41 */
++      HH(d, a, b, c, M[0], s32, 0xeaa127fa);  /* 42 */
++      HH(c, d, a, b, M[3], s33, 0xd4ef3085);  /* 43 */
++      HH(b, c, d, a, M[6], s34, 0x4881d05);   /* 44 */
++      HH(a, b, c, d, M[9], s31, 0xd9d4d039);  /* 45 */
++      HH(d, a, b, c, M[12], s32, 0xe6db99e5); /* 46 */
++      HH(c, d, a, b, M[15], s33, 0x1fa27cf8); /* 47 */
++      HH(b, c, d, a, M[2], s34, 0xc4ac5665);  /* 48 */
++
++      /* Round 4 */
++      II(a, b, c, d, M[0], s41, 0xf4292244);  /* 49 */
++      II(d, a, b, c, M[7], s42, 0x432aff97);  /* 50 */
++      II(c, d, a, b, M[14], s43, 0xab9423a7); /* 51 */
++      II(b, c, d, a, M[5], s44, 0xfc93a039);  /* 52 */
++      II(a, b, c, d, M[12], s41, 0x655b59c3); /* 53 */
++      II(d, a, b, c, M[3], s42, 0x8f0ccc92);  /* 54 */
++      II(c, d, a, b, M[10], s43, 0xffeff47d); /* 55 */
++      II(b, c, d, a, M[1], s44, 0x85845dd1);  /* 56 */
++      II(a, b, c, d, M[8], s41, 0x6fa87e4f);  /* 57 */
++      II(d, a, b, c, M[15], s42, 0xfe2ce6e0); /* 58 */
++      II(c, d, a, b, M[6], s43, 0xa3014314);  /* 59 */
++      II(b, c, d, a, M[13], s44, 0x4e0811a1); /* 60 */
++      II(a, b, c, d, M[4], s41, 0xf7537e82);  /* 61 */
++      II(d, a, b, c, M[11], s42, 0xbd3af235); /* 62 */
++      II(c, d, a, b, M[2], s43, 0x2ad7d2bb);  /* 63 */
++      II(b, c, d, a, M[9], s44, 0xeb86d391);  /* 64 */
++
++      ctx->A += a;
++      ctx->B += b;
++      ctx->C += c;
++      ctx->D += d;
++}
++
++void create_M_blocks(uint32_t * M, uint8_t * data)
++{
++#ifdef HAVE_LITTLE_ENDIAN
++      memcpy((uint8_t *) M, data, 64);
++#endif                                /* HAVE_LITTLE_ENDIAN */
++
++#ifdef HAVE_BIG_ENDIAN
++      int i;
++      for (i = 0; i < 16; i++, data += 4) {
++              ((uint8_t *) (&M[i]))[0] = data[3];
++              ((uint8_t *) (&M[i]))[1] = data[2];
++              ((uint8_t *) (&M[i]))[2] = data[1];
++              ((uint8_t *) (&M[i]))[3] = data[0];
++      }
++#endif                                /* HAVE_BIG_ENDIAN */
++}
++
++void md5_compute(MD5_CTX * ctx, uint8_t * data, uint32_t len)
++{
++      uint8_t pos = ((ctx->bitlen[0] >> 3) & 0x3f);
++
++      /* First we update the bit length */
++      if ((ctx->bitlen[0] += (len << 3)) < (len << 3))
++              ctx->bitlen[1]++;
++      ctx->bitlen[1] += (len >> 29);  /* len is expressed in bytes */
++
++      if (pos) {
++              /* Buffer is not empty */
++              if (64 - pos >= len) {
++                      memcpy(ctx->buf_cur, data, len);
++                      ctx->buf_cur += len;
++                      pos += len;
++                      if (pos == 64) {
++                              /* The current block is over */
++                              md5_over_block(ctx, ctx->buf);
++                              ctx->buf_cur = ctx->buf;
++                      }
++                      return;
++              } else {
++                      memcpy(ctx->buf_cur, data, 64 - pos);
++                      md5_over_block(ctx, ctx->buf);
++                      len -= (64 - pos);
++                      data += (64 - pos);
++                      ctx->buf_cur = ctx->buf;
++              }
++      }
++      while (len >= 64) {
++              md5_over_block(ctx, data);
++              len -= 64;
++              data += 64;
++      }
++      if (len) {
++              memcpy(ctx->buf_cur, data, len);
++              ctx->buf_cur += len;
++      }
++}
++
++void md5_final(MD5_CTX * ctx, uint8_t * digest)
++{
++      uint32_t rem_size;
++      uint8_t *buf_cur = ctx->buf_cur;
++      int i;
++
++      rem_size = 64 - ((ctx->bitlen[0] >> 3) & 0x3f);
++      *(buf_cur++) = 0x80;
++
++      if (rem_size > 8 + 1) {
++              /* We have enough room in the current block */
++              for (i = 0; i < rem_size - 8 - 1; i++) {
++                      *(buf_cur++) = 0;
++              }
++      } else {
++              /* We do not have enough room and need therefore to add a new
++                 64-byte block */
++              for (i = 0; i < rem_size - 1; i++) {
++                      *(buf_cur++) = 0;
++              }
++              md5_over_block(ctx, ctx->buf);
++
++              buf_cur = ctx->buf;
++              for (i = 0; i < 64 - 8; i++) {
++                      *(buf_cur++) = 0;
++              }
++      }
++#ifdef HAVE_LITTLE_ENDIAN
++      memcpy(buf_cur, (uint8_t *) ctx->bitlen, 8);
++#endif                                /* HAVE_LITTLE_ENDIAN */
++
++#ifdef HAVE_BIG_ENDIAN
++      *(buf_cur++) = (ctx->bitlen[0] >> 24) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[0] >> 16) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[0] >> 8) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[0] >> 0) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[1] >> 24) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[1] >> 16) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[1] >> 8) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[1] >> 0) & 0xff;
++#endif                                /* HAVE_BIG_ENDIAN */
++
++      md5_over_block(ctx, ctx->buf);
++
++#ifdef HAVE_LITTLE_ENDIAN
++      memcpy(digest + 0, (uint8_t *) (&(ctx->A)), sizeof(uint32_t));
++      memcpy(digest + 4, (uint8_t *) (&(ctx->B)), sizeof(uint32_t));
++      memcpy(digest + 8, (uint8_t *) (&(ctx->C)), sizeof(uint32_t));
++      memcpy(digest + 12, (uint8_t *) (&(ctx->D)), sizeof(uint32_t));
++#endif                                /* HAVE_LITTLE_ENDIAN */
++
++#ifdef HAVE_BIG_ENDIAN
++      digest[0] = ((ctx->A) >> 24) & 0xff;
++      digest[1] = ((ctx->A) >> 16) & 0xff;
++      digest[2] = ((ctx->A) >> 8) & 0xff;
++      digest[3] = ((ctx->A) >> 0) & 0xff;
++      digest[4] = ((ctx->B) >> 24) & 0xff;
++      digest[5] = ((ctx->B) >> 16) & 0xff;
++      digest[6] = ((ctx->B) >> 8) & 0xff;
++      digest[7] = ((ctx->B) >> 0) & 0xff;
++      digest[8] = ((ctx->C) >> 24) & 0xff;
++      digest[9] = ((ctx->C) >> 16) & 0xff;
++      digest[10] = ((ctx->C) >> 8) & 0xff;
++      digest[11] = ((ctx->C) >> 0) & 0xff;
++      digest[12] = ((ctx->D) >> 24) & 0xff;
++      digest[13] = ((ctx->D) >> 16) & 0xff;
++      digest[14] = ((ctx->D) >> 8) & 0xff;
++      digest[15] = ((ctx->D) >> 0) & 0xff;
++#endif                                /* HAVE_BIG_ENDIAN */
++}
++
++void sha1_init(SHA1_CTX * ctx)
++{
++      ctx->A = 0x67452301;
++      ctx->B = 0xefcdab89;
++      ctx->C = 0x98badcfe;
++      ctx->D = 0x10325476;
++      ctx->E = 0xc3d2e1f0;
++      ctx->buf_cur = ctx->buf;
++      ctx->bitlen[0] = ctx->bitlen[1] = 0;
++      memset(ctx->buf, 0, 64);
++}
++
++void sha1_over_block(SHA1_CTX * ctx, uint8_t * data)
++{
++      int i;
++      uint32_t W[80];
++      uint32_t a = ctx->A;
++      uint32_t b = ctx->B;
++      uint32_t c = ctx->C;
++      uint32_t d = ctx->D;
++      uint32_t e = ctx->E;
++      uint32_t temp;
++
++      create_W_blocks(W, data);
++
++      /* Round 1 */
++      for (i = 0; i < 20; i++) {
++              temp = LROLL(a, 5) + f(b, c, d) + e + W[i] + K1;
++              e = d;
++              d = c;
++              c = LROLL(b, 30);
++              b = a;
++              a = temp;
++      }
++
++      /* Round 2 */
++      for (i = 20; i < 40; i++) {
++              temp = LROLL(a, 5) + h(b, c, d) + e + W[i] + K2;
++              e = d;
++              d = c;
++              c = LROLL(b, 30);
++              b = a;
++              a = temp;
++      }
++
++      /* Round 3 */
++      for (i = 40; i < 60; i++) {
++              temp = LROLL(a, 5) + g(b, c, d) + e + W[i] + K3;
++              e = d;
++              d = c;
++              c = LROLL(b, 30);
++              b = a;
++              a = temp;
++      }
++
++      /* Round 4 */
++      for (i = 60; i < 80; i++) {
++              temp = LROLL(a, 5) + h(b, c, d) + e + W[i] + K4;
++              e = d;
++              d = c;
++              c = LROLL(b, 30);
++              b = a;
++              a = temp;
++      }
++
++      ctx->A += a;
++      ctx->B += b;
++      ctx->C += c;
++      ctx->D += d;
++      ctx->E += e;
++}
++
++void create_W_blocks(uint32_t * W, uint8_t * data)
++{
++      int i;
++
++#ifdef HAVE_BIG_ENDIAN
++      memcpy((uint8_t *) W, data, 64);
++#endif                                /* HAVE_BIG_ENDIAN */
++
++#ifdef HAVE_LITTLE_ENDIAN
++      for (i = 0; i < 16; i++, data += 4) {
++              ((uint8_t *) (&W[i]))[0] = data[3];
++              ((uint8_t *) (&W[i]))[1] = data[2];
++              ((uint8_t *) (&W[i]))[2] = data[1];
++              ((uint8_t *) (&W[i]))[3] = data[0];
++      }
++#endif                                /* HAVE_LITTLE_ENDIAN */
++      for (i = 16; i < 80; i++) {
++              W[i] = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16];
++              W[i] = LROLL(W[i], 1);
++      }
++}
++
++void sha1_compute(SHA1_CTX * ctx, uint8_t * data, uint32_t len)
++{
++      uint8_t pos = ((ctx->bitlen[0] >> 3) & 0x3f);
++
++      /* First we update the bit length */
++      if ((ctx->bitlen[0] += (len << 3)) < (len << 3))
++              ctx->bitlen[1]++;
++      ctx->bitlen[1] += (len >> 29);  /* len is expressed in bytes */
++
++      if (pos) {
++              /* Buffer is not empty */
++              if (64 - pos >= len) {
++                      memcpy(ctx->buf_cur, data, len);
++                      ctx->buf_cur += len;
++                      pos += len;
++                      if (pos == 64) {
++                              /* The current block is over */
++                              sha1_over_block(ctx, ctx->buf);
++                              ctx->buf_cur = ctx->buf;
++                      }
++                      return;
++              } else {
++                      memcpy(ctx->buf_cur, data, 64 - pos);
++                      sha1_over_block(ctx, ctx->buf);
++                      len -= (64 - pos);
++                      data += (64 - pos);
++                      ctx->buf_cur = ctx->buf;
++              }
++      }
++      while (len >= 64) {
++              sha1_over_block(ctx, data);
++              len -= 64;
++              data += 64;
++      }
++      if (len) {
++              memcpy(ctx->buf_cur, data, len);
++              ctx->buf_cur += len;
++      }
++}
++
++void sha1_final(SHA1_CTX * ctx, uint8_t * digest)
++{
++      uint32_t rem_size;
++      uint8_t *buf_cur = ctx->buf_cur;
++      int i;
++
++      rem_size = 64 - ((ctx->bitlen[0] >> 3) & 0x3f);
++      *(buf_cur++) = 0x80;
++
++      if (rem_size > 8 + 1) {
++              /* We have enough room in the current block */
++              for (i = 0; i < rem_size - 8 - 1; i++) {
++                      *(buf_cur++) = 0;
++              }
++      } else {
++              /* We do not have enough room and need therefore to add a new
++                 64-byte block */
++              for (i = 0; i < rem_size - 1; i++) {
++                      *(buf_cur++) = 0;
++              }
++              sha1_over_block(ctx, ctx->buf);
++
++              buf_cur = ctx->buf;
++              for (i = 0; i < 64 - 8; i++) {
++                      *(buf_cur++) = 0;
++              }
++      }
++#ifdef HAVE_BIG_ENDIAN
++      memcpy(buf_cur, (uint8_t *) ctx->bitlen, 8);
++#endif                                /* HAVE_BIG_ENDIAN */
++
++#ifdef HAVE_LITTLE_ENDIAN
++      *(buf_cur++) = (ctx->bitlen[1] >> 24) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[1] >> 16) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[1] >> 8) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[1] >> 0) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[0] >> 24) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[0] >> 16) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[0] >> 8) & 0xff;
++      *(buf_cur++) = (ctx->bitlen[0] >> 0) & 0xff;
++#endif                                /* HAVE_LITTLE_ENDIAN */
++
++      sha1_over_block(ctx, ctx->buf);
++
++#ifdef HAVE_BIG_ENDIAN
++      memcpy(digest + 0, (uint8_t *) (&(ctx->A)), sizeof(uint32_t));
++      memcpy(digest + 4, (uint8_t *) (&(ctx->B)), sizeof(uint32_t));
++      memcpy(digest + 8, (uint8_t *) (&(ctx->C)), sizeof(uint32_t));
++      memcpy(digest + 12, (uint8_t *) (&(ctx->D)), sizeof(uint32_t));
++      memcpy(digest + 16, (uint8_t *) (&(ctx->E)), sizeof(uint32_t));
++#endif                                /* HAVE_BIG_ENDIAN */
++
++#ifdef HAVE_LITTLE_ENDIAN
++      digest[0] = ((ctx->A) >> 24) & 0xff;
++      digest[1] = ((ctx->A) >> 16) & 0xff;
++      digest[2] = ((ctx->A) >> 8) & 0xff;
++      digest[3] = ((ctx->A) >> 0) & 0xff;
++      digest[4] = ((ctx->B) >> 24) & 0xff;
++      digest[5] = ((ctx->B) >> 16) & 0xff;
++      digest[6] = ((ctx->B) >> 8) & 0xff;
++      digest[7] = ((ctx->B) >> 0) & 0xff;
++      digest[8] = ((ctx->C) >> 24) & 0xff;
++      digest[9] = ((ctx->C) >> 16) & 0xff;
++      digest[10] = ((ctx->C) >> 8) & 0xff;
++      digest[11] = ((ctx->C) >> 0) & 0xff;
++      digest[12] = ((ctx->D) >> 24) & 0xff;
++      digest[13] = ((ctx->D) >> 16) & 0xff;
++      digest[14] = ((ctx->D) >> 8) & 0xff;
++      digest[15] = ((ctx->D) >> 0) & 0xff;
++      digest[16] = ((ctx->E) >> 24) & 0xff;
++      digest[17] = ((ctx->E) >> 16) & 0xff;
++      digest[18] = ((ctx->E) >> 8) & 0xff;
++      digest[19] = ((ctx->E) >> 0) & 0xff;
++#endif                                /* HAVE_LITTLE_ENDIAN */
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/hmac.h linux-2.4.25/net/ipv6/mobile_ip6/hmac.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/hmac.h        1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/hmac.h    2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,94 @@
++/*
++ *      MIPL Mobile IPv6 Message authentication algorithms        
++ * 
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _HMAC_H
++#define _HMAC_H
++
++#include <linux/types.h>
++#include <linux/in6.h>
++
++#define HAVE_LITTLE_ENDIAN
++
++#define NO_EXPIRY 1  /* For sec_as */
++
++#define ALG_AUTH_NONE           0
++#define ALG_AUTH_HMAC_MD5       1
++#define ALG_AUTH_HMAC_SHA1      2
++
++struct sec_as;
++struct ah_processing {
++      void *context;
++      struct sec_as *sas;
++      u_int8_t *key_auth;
++      u_int32_t key_auth_len;
++};
++
++struct antireplay {
++      u_int32_t count;
++      u_int32_t bitmap; 
++};
++
++typedef struct {
++  u_int32_t A, B, C, D;
++  u_int32_t bitlen[2];
++  u_int8_t* buf_cur;
++  u_int8_t buf[64];
++} MD5_CTX;
++
++typedef struct {
++  u_int32_t A, B, C, D, E;
++  u_int32_t bitlen[2];
++  u_int8_t* buf_cur;
++  u_int8_t buf[64];
++} SHA1_CTX;
++
++
++
++int ah_hmac_md5_init (struct ah_processing *ahp, u_int8_t *key, u_int32_t key_len);
++void ah_hmac_md5_loop(struct ah_processing*, void*, u_int32_t);
++void ah_hmac_md5_result(struct ah_processing*, char*);
++int ah_hmac_sha1_init(struct ah_processing*, u_int8_t *key, u_int32_t key_len);
++void ah_hmac_sha1_loop(struct ah_processing*, void*, u_int32_t);
++void ah_hmac_sha1_result(struct ah_processing*, char*);
++
++
++#define AH_HDR_LEN 12   /* # of bytes for Next Header, Payload Length,
++                           RESERVED, Security Parameters Index and
++
++                           Sequence Number Field */
++
++void md5_init(MD5_CTX *ctx);
++void md5_over_block(MD5_CTX *ctx, u_int8_t* data);
++void create_M_blocks(u_int32_t* M, u_int8_t* data);
++void md5_compute(MD5_CTX *ctx, u_int8_t* data, u_int32_t len);
++void md5_final(MD5_CTX *ctx, u_int8_t* digest);
++
++void sha1_init(SHA1_CTX *ctx);
++void sha1_over_block(SHA1_CTX *ctx, u_int8_t* data);
++void create_W_blocks(u_int32_t* W, u_int8_t* data);
++void sha1_compute(SHA1_CTX *ctx, u_int8_t* data, u_int32_t len);
++void sha1_final(SHA1_CTX *ctx, u_int8_t* digest);
++
++struct mipv6_acq {
++      struct in6_addr coa;
++      struct in6_addr haddr;
++      struct in6_addr peer;
++      u_int32_t spi;
++};
++#define MIPV6_MAX_AUTH_DATA 20
++
++#define HMAC_MD5_HASH_LEN   16
++#define HMAC_SHA1_HASH_LEN  20
++#define HMAC_SHA1_KEY_SIZE  20
++#define HMAC_MD5_ICV_LEN   12 /* RFC 2403 */
++#define HMAC_SHA1_ICV_LEN  12 /* RFC 2404 */
++
++#endif /* _HMAC_H */
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/ioctl_mn.c linux-2.4.25/net/ipv6/mobile_ip6/ioctl_mn.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/ioctl_mn.c    1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/ioctl_mn.c        2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,142 @@
++/*
++ *    Mobile Node IOCTL Control device
++ *
++ *    Authors:
++ *    Henrik Petander         <lpetande@tml.hut.fi>
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ */
++
++#include <linux/config.h>
++#include <linux/module.h>
++#include <linux/fs.h>
++#include <linux/poll.h>
++#include <linux/ioctl.h> 
++#include <net/ipv6.h>
++#include <asm/uaccess.h>
++
++#include "debug.h"
++#include "mdetect.h"
++#include "multiaccess_ctl.h"
++
++/* Reserved for local / experimental use */
++#define MAJOR_NUM 0xf9
++
++/* Get Care-of address information for Mobile Node */
++#define IOCTL_GET_CAREOFADDR _IOWR(MAJOR_NUM, 9, void *)
++
++#define MA_IOCTL_SET_IFACE_PREFERENCE _IOR (MAJOR_NUM, 13, void *)
++
++/* The name of the device file */
++#define CTLFILE "mipv6_dev"
++
++static int inuse = 0;
++
++static int mipv6_open(struct inode *inode, struct file *file)
++{
++      DEBUG(DBG_INFO, "(%p)\n", file);
++
++      if (inuse)
++              return -EBUSY;
++
++      inuse++;
++
++      MOD_INC_USE_COUNT;
++
++      return 0;
++}
++
++static int mipv6_close(struct inode *inode, struct file *file)
++{
++      DEBUG(DBG_INFO, "(%p,%p)\n", inode, file);
++      inuse--;
++
++      MOD_DEC_USE_COUNT;
++
++      return 0;
++}
++
++int mipv6_ioctl(struct inode *inode, struct file *file, 
++              unsigned int ioctl_num, /* The number of the ioctl */
++              unsigned long arg)      /* The parameter to it */
++{
++      struct in6_addr careofaddr;
++
++      /* Switch according to the ioctl called */
++      switch (ioctl_num) {
++      case IOCTL_GET_CAREOFADDR:
++              DEBUG(DBG_DATADUMP, "IOCTL_GET_CAREOFADDR");
++              /* First get home address from user and then look up 
++               * the care-of address and return it
++               */
++              if (copy_from_user(&careofaddr, (struct in6_addr *)arg, 
++                                 sizeof(struct in6_addr)) < 0) {
++                      DEBUG(DBG_WARNING, "Copy from user failed");
++                      return -EFAULT;
++              }
++              mipv6_get_care_of_address(&careofaddr, &careofaddr);
++              if (copy_to_user((struct in6_addr *)arg, &careofaddr,
++                               sizeof(struct in6_addr)) < 0) {
++                      DEBUG(DBG_WARNING, "copy_to_user failed");
++                      return -EFAULT;
++              }
++              break;
++      case MA_IOCTL_SET_IFACE_PREFERENCE:
++              DEBUG(DBG_INFO, "MA_IOCTL_SET_IFACE_PREFERENCE");
++              ma_ctl_set_preference(arg);
++              break;
++
++      default:
++              DEBUG(DBG_WARNING, "Unknown ioctl cmd (%d)", ioctl_num);
++              return -ENOENT;
++      }
++      return 0;
++}
++
++struct file_operations Fops = {
++      owner: THIS_MODULE,
++      read: NULL,
++      write: NULL,
++      poll: NULL,
++      ioctl: mipv6_ioctl,
++      open: mipv6_open,
++      release: mipv6_close
++};
++
++
++/* Initialize the module - Register the character device */
++int mipv6_ioctl_mn_init(void)
++{
++      int ret_val;
++
++      /* Register the character device (atleast try) */
++      ret_val = register_chrdev(MAJOR_NUM, CTLFILE, &Fops);
++
++      /* Negative values signify an error */
++      if (ret_val < 0) {
++              DEBUG(DBG_ERROR, "failed registering char device (err=%d)",
++                    ret_val);
++              return ret_val;
++      }
++
++      DEBUG(DBG_INFO, "Device number %x, success", MAJOR_NUM);
++      return 0;
++}
++
++
++/* Cleanup - unregister the appropriate file from /proc */
++void mipv6_ioctl_mn_exit(void)
++{
++      int ret;
++      /* Unregister the device */
++      ret = unregister_chrdev(MAJOR_NUM, CTLFILE);
++
++      /* If there's an error, report it */
++      if (ret < 0)
++              DEBUG(DBG_ERROR, "errorcode: %d\n", ret);
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mdetect.c linux-2.4.25/net/ipv6/mobile_ip6/mdetect.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/mdetect.c     1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/mdetect.c 2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,1153 @@
++/*
++ *      Movement Detection Module
++ *
++ *      Authors:
++ *      Henrik Petander                <lpetande@cc.hut.fi>
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ *
++ *      Handles the L3 movement detection of mobile node and also
++ *      changing of its routes.
++ *  
++ */
++
++/*
++ *    Changes:
++ *
++ *    Nanno Langstraat        :       Locking fixes
++ *      Venkata Jagana          :       Locking fix
++ */
++
++#include <linux/autoconf.h>
++#include <linux/errno.h>
++#include <linux/init.h>
++#include <linux/if_arp.h>
++#include <linux/route.h>
++#include <net/ipv6.h>
++#include <net/ip6_route.h>
++#include <net/addrconf.h>
++#include <net/mipglue.h>
++#ifdef CONFIG_SYSCTL
++#include <linux/sysctl.h>
++#endif /* CONFIG_SYSCTL */
++
++#include "util.h"
++#include "mdetect.h"
++#include "mn.h"
++#include "debug.h"
++#include "multiaccess_ctl.h"
++
++#define START 0
++#define CONTINUE 1
++#define OK 2
++#define DEBUG_MDETECT 7
++
++#define DEF_RTR_POLL_IVAL 5 /* In seconds */
++
++#define NO_RTR 0
++#define RTR_SUSPECT 1
++#define CURR_RTR_OK 2
++
++#define RA_RCVD 0
++#define NA_RCVD 1
++#define TIMEOUT 2
++
++#define MIPV6_MDF_NONE 0x0
++#define MIPV6_MDF_HAS_RTR_PREV 0x1
++
++#define ROUTER_REACHABLE 1
++#define RADV_MISSED 2
++#define NOT_REACHABLE 3
++
++/* R_TIME_OUT paramater is used to make the decision when to change the 
++ * default  router, if the current one is unreachable. 2s is pretty aggressive 
++ * and may result in hopping between two routers. OTOH a small value enhances 
++ * the  performance
++ */
++#define R_TIME_OUT 30*HZ
++
++/* maximum RA interval for router unreachability detection */
++#define MAX_RADV_INTERVAL 6*HZ  /* 6000 ms... */
++
++/* Threshold for exponential resending of router solicitations */
++#define RS_RESEND_LINEAR 10*HZ
++
++#define EAGER_CELL_SWITCHING 1
++#define LAZY_CELL_SWITCHING 0
++#define RESPECT_DAD 1
++
++#define ROUTER_ADDRESS 0x20
++
++/* RA flags */
++#define ND_RA_FLAG_MANAGED  0x80
++#define ND_RA_FLAG_OTHER    0x40
++#define ND_RA_FLAG_HA       0x20
++
++/* DAD flags for global and link local addresses */
++
++#define COA_TENTATIVE       0x10
++#define LLADDR_TENTATIVE    0x01
++
++struct router {
++      struct list_head list;
++      struct in6_addr ll_addr;
++      struct in6_addr raddr; /* Also contains prefix */
++      __u8 link_addr[MAX_ADDR_LEN]; /* link layer address */
++      __u8 link_addr_len;
++      __u8 state;
++      __u8 is_current;
++      __u8 reachable;
++      int ifindex;
++      int pfix_len; /* Length of the network prefix */
++      unsigned long lifetime; /* from ra */
++      __u32 last_ns_sent; 
++      __u32 last_ra_rcvd;
++      __u32 interval; /* ra interval in milliseconds, 0 if not set */ 
++      int glob_addr; /*Whether raddr contains also routers global address*/
++      __u8 flags; /* RA flags, for example ha */
++        struct in6_addr CoA;     /* care-off address used with this router */
++      int extra_addr_route;
++};
++
++/* dad could also be RESPECT_DAD for duplicate address detection of
++   new care-of addresses */
++static int dad = 0;
++
++/* Only one choice, nothing else implemented */
++int max_rtr_reach_time = DEF_RTR_POLL_IVAL;
++
++
++int eager_cell_switching = EAGER_CELL_SWITCHING; /* Can be set to 0 via proc */  
++static spinlock_t router_lock; 
++static spinlock_t ho_lock;
++
++static void coa_timer_handler(unsigned long arg);
++static void timer_handler(unsigned long foo);
++static struct router *curr_router = NULL, *next_router = NULL;
++static struct timer_list r_timer = { function: timer_handler };
++static struct timer_list coa_timer = { function: coa_timer_handler };
++#define MAX_ROUTERS 1000
++static LIST_HEAD(rtr_list);
++static int num_routers = 0;
++static struct handoff *_ho = NULL;
++/*
++ * Functions for handling the default router list, which movement
++ * detection uses for avoiding loops etc.
++ */
++
++/* TODO: Send NS to router after MAX interval has passed from last RA */
++static int mipv6_router_state(struct router *rtr) {
++      if (rtr->interval) {
++              if (time_before(jiffies, (rtr->last_ra_rcvd + (rtr->interval * HZ) / 1000)))
++                      return ROUTER_REACHABLE;
++              else
++                      return NOT_REACHABLE;
++      }
++      else
++              if (time_after(jiffies, rtr->last_ra_rcvd + (rtr->lifetime * HZ)))
++                      return NOT_REACHABLE;
++      return ROUTER_REACHABLE;
++}
++
++/* searches for a specific router or any router that is reachable, 
++ * if address is NULL. Also deletes obsolete routers.
++ */
++static void mipv6_router_gc(void)
++{
++      struct router *curr = NULL;
++      struct list_head *lh, *lh_tmp;
++
++      DEBUG_FUNC();
++
++      list_for_each_safe(lh, lh_tmp, &rtr_list) {
++              curr =  list_entry(lh, struct router, list);
++              if (mipv6_router_state(curr) == NOT_REACHABLE && !curr->is_current) {
++                      num_routers--;
++                      list_del_init(&curr->list);
++                      DEBUG(DBG_DATADUMP, "Deleting unreachable router  %x:%x:%x:%x:%x:%x:%x:%x", 
++                            NIPV6ADDR(&curr->raddr));
++                      kfree(curr);
++              }
++              else {
++                      DEBUG(DBG_DATADUMP, "NOT Deleting router  %x:%x:%x:%x:%x:%x:%x:%x", 
++                            NIPV6ADDR(&curr->raddr));
++              }
++      }
++}
++
++static struct router *mipv6_rtr_get(struct in6_addr *search_addr)
++{
++      struct router *rtr = NULL;
++      struct list_head *lh;
++
++      DEBUG_FUNC();
++
++      if (search_addr == NULL)
++              return NULL;
++      list_for_each(lh, &rtr_list) {
++              rtr = list_entry(lh, struct router, list);
++              if(!ipv6_addr_cmp(search_addr, &rtr->raddr)) {
++                      return rtr;
++              }
++      }
++      return NULL;
++}
++
++/*
++ * Adds router to list
++ */
++static struct router *mipv6_rtr_add(struct router *nrt)
++{
++
++      struct router *rptr;
++
++      DEBUG_FUNC();
++
++      /* check if someone is trying DoS attack, or we just have some
++           memory leaks... */
++      if (num_routers > MAX_ROUTERS) {
++              DEBUG(DBG_CRITICAL, 
++                    "failed to add new router, MAX_ROUTERS exceeded");
++              return NULL;
++      }
++      
++      rptr = kmalloc(sizeof(struct router), GFP_ATOMIC);
++      if (rptr) {
++              memcpy(rptr, nrt, sizeof(struct router));
++              list_add(&rptr->list, &rtr_list);
++              num_routers++;
++      }
++      DEBUG(DBG_INFO, "Adding router: %x:%x:%x:%x:%x:%x:%x:%x, "
++            "lifetime : %d sec, adv.interval: %d millisec", 
++            NIPV6ADDR(&rptr->raddr), rptr->lifetime, rptr->interval);
++
++      DEBUG(DBG_INFO, "num_routers after addition: %d", num_routers);
++      return rptr;
++}
++
++/* Cleans up the list */
++static void list_free(struct router **curr_router_p)
++{
++      struct router *tmp;
++      struct list_head *lh, *lh_tmp;
++
++      DEBUG_FUNC();
++
++      DEBUG(DBG_INFO, "Freeing the router list");
++      /* set curr_router->prev_router and curr_router NULL */
++      *curr_router_p = NULL;
++      list_for_each_safe(lh, lh_tmp, &rtr_list) {
++              tmp = list_entry(lh, struct router, list);
++              DEBUG(DBG_INFO, "%x:%x:%x:%x:%x:%x:%x:%x",
++                    NIPV6ADDR(&tmp->ll_addr));
++              list_del(&tmp->list);
++              kfree(tmp);
++              num_routers--;
++      }
++}
++
++int rs_state = START;
++
++/* Sends router solicitations to all valid devices 
++ * source  = link local address (of sending interface)
++ * dstaddr = all routers multicast address
++ * Solicitations are sent at an exponentially decreasing rate
++ *
++ * TODO: send solicitation first at a normal rate (from ipv6) and
++ *       after that use the exponentially increasing intervals 
++ */
++static int rs_send(void)
++{
++      struct net_device *dev;
++      struct in6_addr raddr, lladdr;
++      struct inet6_dev *in6_dev = NULL;
++      static int num_rs;
++
++      if (rs_state == START) {
++              num_rs = 0;
++              rs_state = CONTINUE;
++      } else if (num_rs++ > MAX_RTR_SOLICITATIONS)
++              return HZ;
++
++      ipv6_addr_all_routers(&raddr);
++      read_lock(&dev_base_lock); 
++
++      /*  Send router solicitations to all interfaces  */
++      for (dev = dev_base; dev; dev = dev->next) {
++              if ((dev->flags & IFF_UP) && dev->type == ARPHRD_ETHER) {
++                      DEBUG(DBG_DATADUMP, "Sending RS to device %s", 
++                            dev->name);
++                      if (!ipv6_get_lladdr(dev, &lladdr)) {
++                              ndisc_send_rs(dev, &lladdr, &raddr);
++                              in6_dev = in6_dev_get(dev);
++                              in6_dev->if_flags |= IF_RS_SENT;
++                              in6_dev_put(in6_dev);
++                      } else {
++                              DEBUG(DBG_DATADUMP, "%s: device doesn't have link-local address!\n", dev->name);
++                              continue;
++                      }
++              }
++              
++      }
++      read_unlock(&dev_base_lock);
++      return RTR_SOLICITATION_INTERVAL;
++}
++
++/* Create a new CoA for MN and also add a route to it if it is still tentative 
++   to allow MN to get packets to the address immediately
++ */
++static int form_coa(struct in6_addr *coa, struct in6_addr *pfix, 
++                  int plen, int ifindex)
++{
++      struct net_device *dev;
++      struct inet6_dev *in6_dev;
++      int ret = 0;
++
++      if ((dev = dev_get_by_index(ifindex)) == NULL) {
++              DEBUG(DBG_WARNING, "Device is not present");
++              return -1;
++      }
++      if ((in6_dev = in6_dev_get(dev)) == NULL) {
++              DEBUG(DBG_WARNING, "inet6_dev is not present");
++              dev_put(dev);
++              return -1;
++      }
++      coa->s6_addr32[0] = pfix->s6_addr32[0];
++      coa->s6_addr32[1] = pfix->s6_addr32[1];
++
++      if (ipv6_generate_eui64(coa->s6_addr + 8, dev) &&
++          ipv6_inherit_eui64(coa->s6_addr + 8, in6_dev)) {
++              in6_dev_put(in6_dev);
++              dev_put(dev);
++              return -1;
++      }
++      if (ipv6_chk_addr(coa, dev) == 0) { 
++              DEBUG(DBG_WARNING, "care-of address still tentative");
++              ret = 1;
++      }
++      DEBUG(DBG_INFO, "Formed new CoA:  %x:%x:%x:%x:%x:%x:%x:%x",
++            NIPV6ADDR(coa));
++      
++      in6_dev_put(in6_dev);
++      dev_put(dev);
++      return ret;
++}
++
++static inline int rtr_is_gw(struct router *rtr, struct rt6_info *rt) 
++{
++      return ((rt->rt6i_flags & RTF_GATEWAY) && 
++              !ipv6_addr_cmp(&rt->rt6i_gateway, &rtr->ll_addr));
++}
++
++static inline int is_prefix_route(struct router *rtr, struct rt6_info *rt) 
++{
++      return (!(rt->rt6i_flags & RTF_GATEWAY) &&
++              mipv6_prefix_compare(&rt->rt6i_dst.addr, &rtr->raddr, 
++                                   rtr->pfix_len));
++}
++
++/*
++ * Function that determines whether given rt6_info should be destroyed
++ * (negative => destroy rt6_info, zero or positive => do nothing) 
++ */
++static int mn_route_cleaner(struct rt6_info *rt, void *arg)
++{
++      int type;
++
++      struct router *rtr = (struct router *)arg;
++
++      int ret = -1;
++
++      DEBUG_FUNC();
++      
++      if (!rt || !rtr) {
++              DEBUG(DBG_ERROR, "mn_route_cleaner: rt or rtr NULL");
++              return 0;
++      }
++
++      /* Do not delete routes to local addresses or to multicast
++       * addresses, since we need them to get router advertisements
++       * etc. Multicast addresses are more tricky, but we don't
++       * delete them in any case. The routing mechanism is not optimal for 
++       * multihoming.   
++       *
++       * Also keep all new prefix routes, gateway routes through rtr and
++       * all remaining default routes (including those used for reverse
++       * tunneling)
++       */
++      type = ipv6_addr_type(&rt->rt6i_dst.addr);
++      
++      if ((type & (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL)) ||
++          rt->rt6i_dev == &loopback_dev || rtr_is_gw(rtr, rt) ||
++          is_prefix_route(rtr, rt) || (rt->rt6i_flags & RTF_DEFAULT))  
++              ret = 0;
++      
++      /*   delete all others */
++
++      if (rt->rt6i_dev != &loopback_dev) {
++              DEBUG(DEBUG_MDETECT, 
++                    "%s route:\n"
++                    "dev: %s,\n"
++                    "gw: %x:%x:%x:%x:%x:%x:%x:%x,\n"
++                    "flags: %x,\n"
++                    "metric: %d,\n"
++                    "src: %x:%x:%x:%x:%x:%x:%x:%x,\n"
++                    "dst: %x:%x:%x:%x:%x:%x:%x:%x,\n"
++                    "plen: %d\n",
++                    (ret ? "Deleting" : "Keeping"),
++                    rt->rt6i_dev->name,              
++                    NIPV6ADDR(&rt->rt6i_gateway),            
++                    rt->rt6i_flags,
++                    rt->rt6i_metric,
++                    NIPV6ADDR(&rt->rt6i_src.addr),
++                    NIPV6ADDR(&rt->rt6i_dst.addr),
++                    rt->rt6i_dst.plen);
++      }
++      return ret;
++}
++
++/* 
++ * Deletes old routes 
++ */
++static __inline__ void delete_routes(struct router *rtr)
++{
++      DEBUG_FUNC();
++
++      /* Routing table is locked to ensure that nobody uses its */  
++      write_lock_bh(&rt6_lock);
++      DEBUG(DBG_INFO, "mipv6: Purging routes");
++      /*  TODO: Does not prune, should it?  */
++      fib6_clean_tree(&ip6_routing_table, 
++                      mn_route_cleaner, 0, rtr);
++      write_unlock_bh(&rt6_lock);
++
++}
++
++
++static __inline__ void delete_coas(struct router *rtr)
++{
++      struct net_device *dev;
++      struct inet6_dev *idev;
++      struct inet6_ifaddr *ifa;
++
++      dev = dev_get_by_index(rtr->ifindex);
++      if (!dev)
++              return;
++
++      idev = in6_dev_get(dev);
++      
++      if (idev) {
++              read_lock_bh(&idev->lock);
++              ifa = idev->addr_list;
++              while (ifa) {
++                      int keep; 
++                      spin_lock(&ifa->lock);
++                      
++                      keep = (ifa->flags&(IFA_F_PERMANENT|IFA_F_HOMEADDR) ||
++                              !ipv6_addr_cmp(&ifa->addr, &rtr->CoA));
++                      
++                      spin_unlock(&ifa->lock);
++                      
++                      if (keep)
++                              ifa = ifa->if_next;
++                      else {
++                              in6_ifa_hold(ifa);
++                              read_unlock_bh(&idev->lock);
++                              
++                              ipv6_del_addr(ifa);
++                              
++                              read_lock_bh(&idev->lock);
++                              ifa = idev->addr_list;
++                      }
++              }
++              read_unlock_bh(&idev->lock);
++              in6_dev_put(idev);
++      }
++      dev_put(dev);
++}
++
++int next_mdet_state[3][3] = {{CURR_RTR_OK, NO_RTR, NO_RTR},
++                           {CURR_RTR_OK, CURR_RTR_OK, NO_RTR},
++                           {CURR_RTR_OK, CURR_RTR_OK, RTR_SUSPECT}};
++ 
++char *states[3] = {"NO_RTR", "RTR_SUSPECT", "CURR_RTR_OK"};
++char *events[3] = {"RA_RCVD", "NA_RCVD", "TIMEOUT"};
++
++/* State transitions
++ * NO_RTR, RA_RCVD -> CURR_RTR_OK
++ * NO_RTR, NA_RCVD -> NO_RTR
++ * NO_RTR, TIMEOUT -> NO_RTR
++
++ * RTR_SUSPECT, RA_RCVD -> CURR_RTR_OK
++ * RTR_SUSPECT, NA_RCVD -> CURR_RTR_OK
++ * RTR_SUSPECT, TIMEOUT -> NO_RTR
++
++ * CURR_RTR_OK, RA_RCVD -> CURR_RTR_OK
++ * CURR_RTR_OK, NA_RCVD -> CURR_RTR_OK
++ * CURR_RTR_OK, TIMEOUT -> RTR_SUSPECT
++ */
++static int _curr_state = NO_RTR;
++
++#if 0
++static int get_mdet_state(void){
++      int state;
++      spin_lock_bh(&router_lock); 
++      state = _curr_state;
++      spin_unlock_bh(&router_lock);
++      return state;
++}
++#endif
++
++/* Needs to be called with router_lock locked */
++static int mdet_statemachine(int event)
++{
++      
++      if (event > 2 || _curr_state > 2) {
++             DEBUG(DBG_ERROR, "Got illegal event or curr_state");
++             return -1;
++      }
++
++      DEBUG(DBG_DATADUMP, "Got event %s and curr_state is %s", 
++            events[event], states[_curr_state]); 
++      
++      _curr_state = next_mdet_state[_curr_state][event];
++      DEBUG(DBG_DATADUMP, "Next state is %s", states[_curr_state]);
++      return _curr_state;
++}
++
++static void mipv6_do_ll_dad(int ifindex)
++{
++      struct net_device *dev = dev_get_by_index(ifindex);
++      if (dev) {
++              struct in6_addr lladdr;
++              struct inet6_ifaddr *ifa;
++              if (!ipv6_get_lladdr(dev, &lladdr) &&
++                  (ifa = ipv6_get_ifaddr(&lladdr, dev)) != NULL) {
++                      spin_lock_bh(&ifa->lock);
++                      if (!(ifa->flags & IFA_F_TENTATIVE)) {
++                              ifa->flags |= IFA_F_TENTATIVE;
++                              spin_unlock_bh(&ifa->lock);
++                              addrconf_dad_start(ifa, 0);
++                      } else
++                              spin_unlock_bh(&ifa->lock);
++
++              }
++              dev_put(dev);
++      }
++}
++/* 
++ * Changes the router, called from ndisc.c if mipv6_router_event 
++ * returns true.
++ */
++
++static void mipv6_change_router(void)
++{
++      struct in6_addr coa;
++      int ret, ifindex;
++      
++      DEBUG_FUNC(); 
++
++      
++      if (next_router == NULL) 
++              return;
++      
++      spin_lock(&router_lock);
++
++
++      if (curr_router != NULL && 
++          !ipv6_addr_cmp(&curr_router->ll_addr, &next_router->ll_addr)) {
++              DEBUG(DBG_INFO,"Trying to handoff from: "
++                    "%x:%x:%x:%x:%x:%x:%x:%x",
++                    NIPV6ADDR(&curr_router->ll_addr));
++              DEBUG(DBG_INFO,"Trying to handoff to: "
++                    "%x:%x:%x:%x:%x:%x:%x:%x",
++                    NIPV6ADDR(&next_router->ll_addr));
++              next_router = NULL; /* Let's not leave dangling pointers */
++              spin_unlock(&router_lock);
++              return;
++        }
++      ret = form_coa(&next_router->CoA, &next_router->raddr, 
++                     next_router->pfix_len, next_router->ifindex);
++      if (ret < 0) {
++              DEBUG(DBG_ERROR, "handoff: Creation of coa failed");
++              spin_unlock(&router_lock);
++              return;
++      } else if (ret > 0)
++              next_router->flags |= COA_TENTATIVE;
++
++      mdet_statemachine(RA_RCVD); /* TODO: What if DAD fails... */
++      if (next_router->interval)
++              mod_timer(&r_timer, jiffies + 
++                        (next_router->interval * HZ)/1000);
++      else
++              mod_timer(&r_timer, jiffies + max_rtr_reach_time * HZ);
++      
++
++      if (ret == 0) {
++              ipv6_addr_copy(&coa, &next_router->CoA);
++              ifindex = next_router->ifindex;
++              spin_unlock(&router_lock);
++              mipv6_mdet_finalize_ho(&coa, ifindex);
++              return;
++      }
++      spin_unlock(&router_lock);
++
++}
++static unsigned long ns_send(void)
++{
++      struct neighbour *neigh;
++      struct net_device *dev;
++      struct in6_addr *raddr;
++
++      DEBUG(DBG_DATADUMP, "Sending Neighbour solicitation to default router to verify its reachability");
++      if (!curr_router) 
++              return HZ;
++      if ((dev = dev_get_by_index(curr_router->ifindex)) == NULL)
++              return HZ;
++      if ((neigh = ndisc_get_neigh(dev, &curr_router->ll_addr)) == NULL) {
++              dev_put(dev);
++              return HZ;
++      }
++      if (curr_router->glob_addr)
++              raddr = &curr_router->raddr;
++      else 
++              raddr = &curr_router->ll_addr;
++
++      curr_router->last_ns_sent = jiffies;
++      ndisc_send_ns(dev, neigh, raddr, raddr, NULL);  
++
++      neigh_release(neigh);
++      dev_put(dev);
++      return HZ/5; /* Wait 200ms for a reply */
++}
++
++static int na_rcvd(void)
++{     
++      int neigh_ok = 0;
++      struct neighbour *neigh;
++      struct net_device *dev;
++
++      if (!curr_router) 
++              return 0;
++      if ((dev = dev_get_by_index(curr_router->ifindex)) == NULL)
++              return 0;
++      if ((neigh = ndisc_get_neigh(dev, &curr_router->ll_addr)) == NULL) {
++              dev_put(dev);
++              return 0;
++      }
++      if (neigh->flags & NTF_ROUTER && 
++          (time_after(neigh->confirmed, curr_router->last_ns_sent) || 
++           neigh->confirmed == curr_router->last_ns_sent)) {
++              neigh_ok = 1;
++              DEBUG(DBG_DATADUMP, "Mdetect event: NA rcvd from curr rtr");
++      } else
++              DEBUG(DBG_DATADUMP, "Mdetect event: NA NOT rcvd from curr rtr within time limit");
++      neigh_release(neigh);
++      dev_put(dev);
++      return neigh_ok;
++}
++
++static void coa_timer_handler(unsigned long dummy)
++{
++
++      spin_lock_bh(&ho_lock);
++      if (_ho) {
++              DEBUG(DBG_INFO, "Starting handoff after DAD");
++              mipv6_mobile_node_moved(_ho);
++              kfree(_ho);
++              _ho = NULL;
++      }
++      spin_unlock_bh(&ho_lock);
++}
++static void timer_handler(unsigned long foo)
++{
++      unsigned long timeout;
++      int state;
++      spin_lock_bh(&router_lock);
++      
++      if (_curr_state != NO_RTR)
++              rs_state = START;
++
++      if (_curr_state == RTR_SUSPECT && na_rcvd()) {
++              state = mdet_statemachine(NA_RCVD);
++              timeout = curr_router->interval ? curr_router->interval : max_rtr_reach_time * HZ;
++      } else { 
++              state =  mdet_statemachine(TIMEOUT);
++              if (state == NO_RTR)
++                      timeout = rs_send();
++              else  /* RTR_SUSPECT */
++                      timeout = ns_send();
++      }
++      if (!timeout)
++              timeout = HZ;
++
++      mipv6_router_gc();
++      mod_timer(&r_timer, jiffies + timeout);
++      spin_unlock_bh(&router_lock);
++}
++
++/**
++ * mipv6_get_care_of_address - get node's care-of primary address
++ * @homeaddr: one of node's home addresses
++ * @coaddr: buffer to store care-of address
++ *
++ * Stores the current care-of address in the @coaddr, assumes
++ * addresses in EUI-64 format.  Since node might have several home
++ * addresses caller MUST supply @homeaddr.  If node is at home
++ * @homeaddr is stored in @coaddr.  Returns 0 on success, otherwise a
++ * negative value.
++ **/
++int mipv6_get_care_of_address(
++      struct in6_addr *homeaddr, struct in6_addr *coaddr)
++{
++      
++      DEBUG_FUNC();
++
++      if (homeaddr == NULL)
++              return -1;
++      spin_lock_bh(&router_lock);
++      if (curr_router == NULL || mipv6_mn_is_at_home(homeaddr) || 
++          mipv6_prefix_compare(homeaddr, &curr_router->raddr, 64) || 
++          curr_router->flags&COA_TENTATIVE) {
++              DEBUG(DBG_INFO,
++                    "mipv6_get_care_of_address: returning home address");
++              ipv6_addr_copy(coaddr, homeaddr);
++              spin_unlock_bh(&router_lock);
++              return 0;
++
++      }
++
++      /* At home or address check failure probably due to dad wait */
++      if (mipv6_prefix_compare(&curr_router->raddr, homeaddr, 
++                               curr_router->pfix_len) 
++                               || (dad == RESPECT_DAD && 
++                                   (ipv6_chk_addr(coaddr, NULL) == 0))) { 
++              ipv6_addr_copy(coaddr, homeaddr);
++      } else { 
++              ipv6_addr_copy(coaddr, &curr_router->CoA);
++      }
++
++      spin_unlock_bh(&router_lock);
++      return 0;
++}
++
++int mipv6_mdet_del_if(int ifindex)
++{
++      struct router *curr = NULL;
++      struct list_head *lh, *lh_tmp;
++
++      spin_lock_bh(&router_lock);
++      list_for_each_safe(lh, lh_tmp, &rtr_list) {
++              curr =  list_entry(lh, struct router, list);
++              if (curr->ifindex == ifindex) {
++                      num_routers--;
++                      list_del_init(&curr->list);
++                      DEBUG(DBG_DATADUMP, "Deleting router  %x:%x:%x:%x:%x:%x:%x:%x on interface %d", 
++                            NIPV6ADDR(&curr->raddr), ifindex);
++                      if (curr_router == curr)
++                              curr_router = NULL;
++                      kfree(curr);
++              }
++      }
++      spin_unlock_bh(&router_lock);
++      return 0;
++}
++
++void mipv6_mdet_retrigger_ho(void)
++{
++      struct handoff ho;
++
++      spin_lock_bh(&router_lock);
++      if (curr_router != NULL) {
++              ho.coa = &curr_router->CoA;
++              ho.plen = curr_router->pfix_len;
++              ho.ifindex = curr_router->ifindex;
++              ipv6_addr_copy(&ho.rtr_addr, &curr_router->raddr);
++              ho.home_address = (curr_router->glob_addr && 
++                                 curr_router->flags&ND_RA_FLAG_HA);
++      }
++      spin_unlock_bh(&router_lock);
++      mipv6_mobile_node_moved(&ho);
++}
++
++void mipv6_mdet_set_curr_rtr_reachable(int reachable)
++{
++      spin_lock_bh(&router_lock);
++      if (curr_router != NULL) {
++              curr_router->reachable = reachable;
++      }
++      spin_unlock_bh(&router_lock);
++
++}
++
++int mipv6_mdet_finalize_ho(const struct in6_addr *coa, const int ifindex)
++{
++      int dummy;
++      struct handoff ho;
++      struct router *tmp;
++      struct net_device *dev; 
++      struct in6_addr ll_addr;
++
++      spin_lock_bh(&router_lock);
++
++      if (!next_router) {
++              spin_unlock_bh(&router_lock);
++              return 0;
++      }
++
++      dev = dev_get_by_index(next_router->ifindex);
++
++      if (ipv6_get_lladdr(dev, &ll_addr) == 0) {
++              if (ipv6_addr_cmp(&ll_addr, coa) == 0)
++                      DEBUG(DBG_INFO, "DAD for link local address completed");
++              next_router->flags &= ~LLADDR_TENTATIVE;
++      }
++
++      dev_put(dev);
++
++      if (mipv6_prefix_compare(coa, &next_router->CoA, 
++                               next_router->pfix_len)) {
++              DEBUG(DBG_INFO, "DAD for Care-of address completed");
++              next_router->flags &= ~COA_TENTATIVE;
++      }
++      if (!(next_router->flags&LLADDR_TENTATIVE) && !(next_router->flags&COA_TENTATIVE)) {
++              DEBUG(DBG_INFO, "%s: Proceeding with handoff after DAD\n", __FUNCTION__);
++              tmp = curr_router;
++              curr_router = next_router;
++              curr_router->is_current = 1;
++              next_router = NULL; 
++              curr_router->flags &= ~COA_TENTATIVE; 
++              delete_routes(curr_router);
++              delete_coas(curr_router);
++              if (tmp) {
++                      struct net_device *dev_old = dev_get_by_index(tmp->ifindex);
++                      struct rt6_info *rt = NULL;
++                      if (dev_old) {
++                              rt = rt6_get_dflt_router(&tmp->ll_addr, dev_old);
++                              dev_put(dev_old);
++                      }
++                      if (rt)
++                              ip6_del_rt(rt, NULL);
++                      tmp->is_current = 0;
++              }
++
++              ma_ctl_upd_iface(curr_router->ifindex, MA_IFACE_CURRENT, &dummy);
++              ma_ctl_upd_iface(curr_router->ifindex, MA_IFACE_CURRENT, &dummy);
++
++
++              ho.coa = &curr_router->CoA;
++              ho.plen = curr_router->pfix_len;
++              ho.ifindex = curr_router->ifindex;
++              ipv6_addr_copy(&ho.rtr_addr, &curr_router->raddr);
++              ho.home_address = (curr_router->glob_addr && 
++                                  curr_router->flags&ND_RA_FLAG_HA);
++              
++              spin_unlock_bh(&router_lock);
++              mipv6_mobile_node_moved(&ho);
++      } else 
++              spin_unlock_bh(&router_lock);
++      return 0;
++}
++/* Decides whether router candidate is the same router as current rtr
++ * based on prefix / global addresses of the routers and their link local 
++ * addresses 
++ */
++static int is_current_rtr(struct router *nrt, struct router *crt)
++{
++      DEBUG_FUNC();
++      
++      DEBUG(DEBUG_MDETECT, "Current router: "
++            "%x:%x:%x:%x:%x:%x:%x:%x and", NIPV6ADDR(&crt->raddr));
++      DEBUG(DEBUG_MDETECT, "Candidate router: "
++            "%x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(&nrt->raddr));
++
++      return (!ipv6_addr_cmp(&nrt->raddr,&crt->raddr) && 
++              !ipv6_addr_cmp(&nrt->ll_addr, &crt->ll_addr));
++}
++
++/* 
++ * Change next router to nrtr
++ * Returns 1, if router has been changed.
++ */ 
++
++static int change_next_rtr(struct router *nrtr, struct router *ortr)
++{
++      int changed = 0;
++      DEBUG_FUNC();
++
++      if (!next_router || ipv6_addr_cmp(&nrtr->raddr, &next_router->raddr)) {
++              changed = 1;
++      }
++      next_router = nrtr;
++      return changed;
++}
++static int clean_ncache(struct router *nrt, struct router *ort, int same_if)
++{
++      struct net_device *ortdev;
++      DEBUG_FUNC();
++
++      /* Always call ifdown after a handoff to ensure proper routing */
++      
++      if (!ort) 
++              return 0;
++      if ((ortdev = dev_get_by_index(ort->ifindex)) == NULL) {
++              DEBUG(DBG_WARNING, "Device is not present");
++              return -1;
++      }
++      neigh_ifdown(&nd_tbl, ortdev);
++      dev_put(ortdev);        
++      return 0;
++}
++
++static int mdet_get_if_preference(int ifi)
++{
++      int pref = 0;
++
++      DEBUG_FUNC();
++
++      pref = ma_ctl_get_preference(ifi);
++
++      DEBUG(DEBUG_MDETECT, "ifi: %d preference %d", ifi, pref);
++
++      return pref;
++}
++
++/*
++ * Called from mipv6_mn_ra_rcv to determine whether to do a handoff. 
++ */
++static int mipv6_router_event(struct router *rptr)
++{
++      struct router *nrt = NULL;
++      int new_router = 0, same_if = 1;
++      int oldstate = _curr_state;
++      int addrtype = ipv6_addr_type(&rptr->raddr);
++
++      DEBUG_FUNC();
++
++      if (rptr->lifetime == 0)
++              return MIPV6_IGN_RTR;
++      DEBUG(DEBUG_MDETECT, "Received a RA from router: "
++            "%x:%x:%x:%x:%x:%x:%x:%x", NIPV6ADDR(&rptr->raddr));
++      spin_lock(&router_lock);
++      
++      /* Add or update router entry */
++      if ((nrt = mipv6_rtr_get(&rptr->raddr)) == NULL) {
++              if (addrtype == IPV6_ADDR_ANY || (nrt = mipv6_rtr_add(rptr)) == NULL) {
++                              spin_unlock(&router_lock);
++                              return MIPV6_IGN_RTR;
++              }
++              DEBUG(DBG_INFO, "Router not on list,adding it to the list"); 
++              new_router = 1;
++      }
++      nrt->last_ra_rcvd = jiffies;
++      nrt->state = ROUTER_REACHABLE;
++      nrt->interval = rptr->interval;
++      nrt->lifetime = rptr->lifetime;
++      nrt->ifindex = rptr->ifindex;
++      nrt->flags = rptr->flags;
++      nrt->glob_addr = rptr->glob_addr;
++
++      /* Whether from current router */
++      if (curr_router && curr_router->reachable && 
++          is_current_rtr(nrt, curr_router)) {
++              if (nrt->interval)
++                      mod_timer(&r_timer, jiffies + (nrt->interval * HZ)/1000);
++              else
++                      mod_timer(&r_timer, jiffies + max_rtr_reach_time * HZ);
++              mdet_statemachine(RA_RCVD);
++              spin_unlock(&router_lock);
++              return MIPV6_ADD_RTR;
++      } else if (oldstate == NO_RTR) {
++              rt6_purge_dflt_routers(0); /* For multiple interface case */
++              DEBUG(DBG_INFO, "No router or router not reachable, switching to new one");   
++              goto handoff;
++      }
++      if (!curr_router) { 
++              /* Startup */
++              goto handoff;
++      }
++      /* Router behind same interface as current one ?*/
++      same_if = (nrt->ifindex == curr_router->ifindex);
++      /* Switch to new router behind same interface if eager cell 
++       *  switching is used or if the interface is preferred
++       */
++      if ((new_router && eager_cell_switching && same_if) ||
++          (mdet_get_if_preference(nrt->ifindex) > 
++           mdet_get_if_preference(curr_router->ifindex))) {
++              DEBUG(DBG_INFO, "Switching to new router.");
++              goto handoff;
++      }
++      
++      /* No handoff, don't add default route */
++      DEBUG(DEBUG_MDETECT, "Ignoring RA");
++      spin_unlock(&router_lock);
++      return MIPV6_IGN_RTR;
++handoff:
++      clean_ncache(nrt, curr_router, same_if);
++      nrt->reachable = 1;
++      if (same_if && change_next_rtr(nrt, curr_router)) {
++              mipv6_do_ll_dad(nrt->ifindex);
++              nrt->flags |= LLADDR_TENTATIVE;
++      }
++      spin_unlock(&router_lock);
++
++      return MIPV6_CHG_RTR;
++}     
++
++/* 
++ * Called from ndisc.c's router_discovery.
++ */
++
++static inline int ret_to_ha(struct in6_addr *addr)
++{
++      int res = 0;
++      struct mn_info *minfo;
++      read_lock(&mn_info_lock);
++      minfo = mipv6_mninfo_get_by_ha(addr);
++      if (minfo != NULL) {
++              spin_lock(&minfo->lock);
++              if (minfo->has_home_reg) {
++                      res = 1;
++              }
++              spin_unlock(&minfo->lock);
++      }
++      read_unlock(&mn_info_lock);
++      return res;
++}
++
++static int mipv6_mn_ra_rcv(struct sk_buff *skb, struct ndisc_options *ndopts)
++{
++      int ifi = ((struct inet6_skb_parm *)skb->cb)->iif;
++      struct ra_msg *ra = (struct ra_msg *) skb->h.raw;
++      struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
++      struct router nrt;
++      struct in6_addr *ha = NULL;
++      u8 *lladdr = NULL;
++      int res;
++      DEBUG_FUNC();
++
++      memset(&nrt, 0, sizeof(struct router));
++
++      if (ra->icmph.icmp6_home_agent) {
++              nrt.flags |= ND_RA_FLAG_HA;
++              DEBUG(DBG_DATADUMP, "RA has ND_RA_FLAG_HA up");
++      }
++
++      if (ra->icmph.icmp6_addrconf_managed) {
++              nrt.flags |= ND_RA_FLAG_MANAGED;
++              DEBUG(DBG_DATADUMP, "RA has ND_RA_FLAG_MANAGED up");
++      }
++
++      if (ra->icmph.icmp6_addrconf_other) {
++              nrt.flags |= ND_RA_FLAG_OTHER;
++              DEBUG(DBG_DATADUMP, "RA has ND_RA_FLAG_OTHER up");
++      }
++
++      ipv6_addr_copy(&nrt.ll_addr, saddr);
++      nrt.ifindex = ifi;
++      nrt.lifetime = ntohs(ra->icmph.icmp6_rt_lifetime);
++
++      if (ndopts->nd_opts_src_lladdr) {
++              lladdr = (u8 *) ndopts->nd_opts_src_lladdr+2;
++              nrt.link_addr_len = skb->dev->addr_len;
++              memcpy(nrt.link_addr, lladdr, nrt.link_addr_len);
++      }
++      if (ndopts->nd_opts_pi) {
++              struct nd_opt_hdr *p;
++              for (p = ndopts->nd_opts_pi;
++                   p;
++                   p = ndisc_next_option(p, ndopts->nd_opts_pi_end)) {
++                      struct prefix_info *pinfo;
++                      int update = 0;
++
++                      pinfo = (struct prefix_info *) p;
++
++                      if (!pinfo->autoconf)
++                              continue;
++
++                      if ((pinfo->router_address && 
++                           (update = ret_to_ha(&pinfo->prefix))) ||
++                          ipv6_addr_type(&nrt.raddr) != IPV6_ADDR_UNICAST) {
++                              ipv6_addr_copy(&nrt.raddr, &pinfo->prefix);
++                              nrt.pfix_len = pinfo->prefix_len;
++                              if (pinfo->router_address)
++                                      nrt.glob_addr = 1;
++                              else
++                                      nrt.glob_addr = 0;
++                              if (update)
++                                      ha = &pinfo->prefix;
++                              DEBUG(DBG_DATADUMP, "Address of the received "
++                                    "prefix info option: %x:%x:%x:%x:%x:%x:%x:%x", 
++                                    NIPV6ADDR(&nrt.raddr));
++                              DEBUG(DBG_DATADUMP, "the length of the prefix is %d", 
++                                    nrt.pfix_len);
++                      }
++              }
++      }
++      if (ndopts->nd_opts_rai) {                      
++              nrt.interval = ntohl(*(__u32 *)(ndopts->nd_opts_rai+4));
++              DEBUG(DBG_DATADUMP, 
++                    "received router interval option with interval : %d ",
++                    nrt.interval / HZ);
++
++              if (nrt.interval > MAX_RADV_INTERVAL) {
++                      nrt.interval = 0;
++                      DEBUG(DBG_DATADUMP, "but we are using: %d, "
++                            "because interval>MAX_RADV_INTERVAL",
++                            nrt.interval / HZ);
++              }
++      }
++
++      res = mipv6_router_event(&nrt);
++      
++      if (ha && lladdr) {
++              mipv6_mn_ha_nd_update(__dev_get_by_index(ifi), ha, lladdr);
++      }
++      return res;
++}
++
++int __init mipv6_initialize_mdetect(void)
++{
++
++      DEBUG_FUNC();
++
++      spin_lock_init(&router_lock);
++      spin_lock_init(&ho_lock);
++      init_timer(&coa_timer);
++      init_timer(&r_timer);
++      r_timer.expires = jiffies + HZ;
++      add_timer(&r_timer);
++
++      /* Actual HO, also deletes old routes after the addition of new ones 
++         in ndisc */
++      MIPV6_SETCALL(mipv6_change_router, mipv6_change_router);
++
++      MIPV6_SETCALL(mipv6_ra_rcv, mipv6_mn_ra_rcv);
++
++      return 0;
++}
++
++int __exit mipv6_shutdown_mdetect()
++{
++
++      DEBUG_FUNC();
++
++      MIPV6_RESETCALL(mipv6_ra_rcv);
++      MIPV6_RESETCALL(mipv6_change_router);
++      spin_lock_bh(&router_lock);
++      spin_lock(&ho_lock);
++      del_timer(&coa_timer);
++      del_timer(&r_timer);
++      /* Free the memory allocated by router list */
++      list_free(&curr_router);
++      if (_ho)
++              kfree(_ho);
++      spin_unlock(&ho_lock);
++      spin_unlock_bh(&router_lock);
++      return 0;
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mdetect.h linux-2.4.25/net/ipv6/mobile_ip6/mdetect.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/mdetect.h     1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/mdetect.h 2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,37 @@
++/*
++ *      MIPL Mobile IPv6 Movement detection module header file
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _MDETECT_H
++#define _MDETECT_H
++
++struct handoff {
++      int home_address; /* Is the coa a home address */
++      int ifindex;
++      int plen;
++      struct in6_addr *coa;
++      struct in6_addr rtr_addr; /* Prefix or rtr address if coa is home address */
++};
++
++int mipv6_initialize_mdetect(void);
++
++int mipv6_shutdown_mdetect(void);
++
++int mipv6_get_care_of_address(struct in6_addr *homeaddr, struct in6_addr *coa);
++
++int mipv6_mdet_del_if(int ifindex);
++
++int mipv6_mdet_finalize_ho(const struct in6_addr *coa, const int ifindex);
++
++void mipv6_mdet_retrigger_ho(void);
++
++void mipv6_mdet_set_curr_rtr_reachable(int reachable);
++
++#endif /* _MDETECT_H */
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp.c linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp.c  1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp.c      2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,342 @@
++/**
++ * Generic icmp routines
++ *
++ * Authors:
++ * Jaakko Laine <medved@iki.fi>,
++ * Ville Nuorvala <vnuorval@tcs.hut.fi> 
++ *
++ * $Id$
++ *
++ * 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 (at your option) any later version.
++ */
++
++#include <linux/config.h>
++#include <linux/icmpv6.h>
++#include <net/checksum.h>
++#include <net/ipv6.h>
++#include <net/ip6_route.h>
++#include <net/mipv6.h>
++#include <net/mipglue.h>
++
++#include "debug.h"
++#include "bcache.h"
++#include "mipv6_icmp.h"
++#include "config.h"
++
++struct mipv6_icmpv6_msg {
++      struct icmp6hdr icmph;
++      __u8 *data;
++      struct in6_addr *daddr;
++      int len;
++      __u32 csum;
++};
++
++#define MIPV6_ICMP_HOP_LIMIT 64
++
++static struct socket *mipv6_icmpv6_socket = NULL;
++static __u16 identifier = 0;
++
++int mipv6_icmpv6_no_rcv(struct sk_buff *skb)
++{
++      return 0;
++}
++
++static int mipv6_icmpv6_xmit_holder = -1;
++
++static int mipv6_icmpv6_xmit_lock_bh(void)
++{
++      if (!spin_trylock(&mipv6_icmpv6_socket->sk->lock.slock)) {
++              if (mipv6_icmpv6_xmit_holder == smp_processor_id())
++                      return -EAGAIN;
++              spin_lock(&mipv6_icmpv6_socket->sk->lock.slock);
++      }
++      mipv6_icmpv6_xmit_holder = smp_processor_id();
++      return 0;
++}
++
++static __inline__ int mipv6_icmpv6_xmit_lock(void)
++{
++      int ret;
++      local_bh_disable();
++      ret = mipv6_icmpv6_xmit_lock_bh();
++      if (ret)
++              local_bh_enable();
++      return ret;
++}
++
++static void mipv6_icmpv6_xmit_unlock_bh(void)
++{
++      mipv6_icmpv6_xmit_holder = -1;
++      spin_unlock(&mipv6_icmpv6_socket->sk->lock.slock);
++}
++
++static __inline__ void mipv6_icmpv6_xmit_unlock(void)
++{
++      mipv6_icmpv6_xmit_unlock_bh();
++      local_bh_enable();
++}
++
++
++/**
++ * mipv6_icmpv6_dest_unreach - Destination Unreachable ICMP error message handler
++ * @skb: buffer containing ICMP error message
++ *
++ * Special Mobile IPv6 ICMP handling.  If Correspondent Node receives
++ * persistent ICMP Destination Unreachable messages for a destination
++ * in its Binding Cache, the binding should be deleted.  See draft
++ * section 8.8.
++ **/
++static int mipv6_icmpv6_rcv_dest_unreach(struct sk_buff *skb)
++{
++      struct icmp6hdr *icmph = (struct icmp6hdr *) skb->h.raw;
++      struct ipv6hdr *ipv6h = (struct ipv6hdr *) (icmph + 1);
++      int left = (skb->tail - skb->h.raw) - sizeof(*icmph)- sizeof(ipv6h);
++      struct ipv6_opt_hdr *eh;
++      struct rt2_hdr *rt2h = NULL;
++      struct in6_addr *daddr = &ipv6h->daddr;
++      struct in6_addr *saddr = &ipv6h->saddr;
++      int hdrlen, nexthdr = ipv6h->nexthdr;
++      struct mipv6_bce bce;
++      DEBUG_FUNC();
++
++      eh = (struct ipv6_opt_hdr *) (ipv6h + 1);
++
++      while (left > 0) {
++              if (nexthdr != NEXTHDR_HOP && nexthdr != NEXTHDR_DEST && 
++                  nexthdr != NEXTHDR_ROUTING)
++                      return 0;
++
++              hdrlen = ipv6_optlen(eh);
++              if (hdrlen > left)
++                      return 0;
++
++              if (nexthdr == NEXTHDR_ROUTING) {
++                      struct ipv6_rt_hdr *rth = (struct ipv6_rt_hdr *) eh;
++
++                      if (rth->type == IPV6_SRCRT_TYPE_2) {
++                              if (hdrlen != sizeof(struct rt2_hdr))
++                                      return 0;
++
++                              rt2h = (struct rt2_hdr *) rth;
++
++                              if (rt2h->rt_hdr.segments_left > 0)
++                                      daddr = &rt2h->addr;
++                              break;
++                      }
++              }
++              /* check for home address option in case this node is a MN */
++              if (nexthdr == NEXTHDR_DEST) {
++                      __u8 *raw = (__u8 *) eh;
++                      __u16 i = 2;
++                      while (1) {
++                              struct mipv6_dstopt_homeaddr *hao;
++                              
++                              if (i + sizeof (*hao) > hdrlen)
++                                      break;
++                              
++                              hao = (struct mipv6_dstopt_homeaddr *) &raw[i];
++                              
++                              if (hao->type == MIPV6_TLV_HOMEADDR &&
++                                  hao->length == sizeof(struct in6_addr)) {
++                                      saddr = &hao->addr;
++                                      break;
++                              }
++                              if (hao->type)
++                                      i += hao->length + 2;
++                              else
++                                      i++;
++                      }
++                      
++              }
++              nexthdr = eh->nexthdr;
++              eh = (struct ipv6_opt_hdr *) ((u8 *) eh + hdrlen);
++              left -= hdrlen;
++      }
++      if (rt2h == NULL) return 0;
++
++      if (mipv6_bcache_get(daddr, saddr, &bce) == 0 && !(bce.flags&HOME_REGISTRATION)) {
++              /* A primitive algorithm for detecting persistent ICMP destination unreachable messages */
++              if (bce.destunr_count &&
++                  time_after(jiffies, 
++                             bce.last_destunr + MIPV6_DEST_UNR_IVAL*HZ)) 
++                      bce.destunr_count = 0;
++
++              bce.destunr_count++;
++
++              mipv6_bcache_icmp_err(daddr, saddr, bce.destunr_count);
++
++              if (bce.destunr_count > MIPV6_MAX_DESTUNREACH && mipv6_bcache_delete(daddr, saddr, CACHE_ENTRY) == 0) {
++                      DEBUG(DBG_INFO, "Deleted bcache entry "
++                            "%x:%x:%x:%x:%x:%x:%x:%x "
++                            "%x:%x:%x:%x:%x:%x:%x:%x (reason: "
++                            "%d dest unreachables) ",
++                            NIPV6ADDR(daddr), NIPV6ADDR(saddr), bce.destunr_count);
++              }
++      }
++      return 0;
++}
++
++static int mipv6_icmpv6_getfrag(const void *data, struct in6_addr *saddr, 
++                              char *buff, unsigned int offset, 
++                              unsigned int len)
++{
++      struct mipv6_icmpv6_msg *msg = (struct mipv6_icmpv6_msg *) data;
++      struct icmp6hdr *icmph;
++      __u32 csum;
++
++      if (offset) {
++              msg->csum = csum_partial_copy_nocheck(msg->data + offset -
++                                                    sizeof(*icmph), buff,
++                                                    len, msg->csum);
++              return 0;
++      }
++      
++      csum = csum_partial_copy_nocheck((__u8 *) &msg->icmph, buff,
++                                       sizeof(*icmph), msg->csum);
++      
++      csum = csum_partial_copy_nocheck(msg->data, buff + sizeof(*icmph),
++                                       len - sizeof(*icmph), csum);
++      
++      icmph = (struct icmp6hdr *) buff;
++      
++      icmph->icmp6_cksum = csum_ipv6_magic(saddr, msg->daddr, msg->len,
++                                           IPPROTO_ICMPV6, csum);
++      return 0; 
++}
++
++/**
++ * mipv6_icmpv6_send - generic icmpv6 message send
++ * @daddr: destination address
++ * @saddr: source address
++ * @type: icmp type
++ * @code: icmp code
++ * @id: packet identifier. If null, uses internal counter to get new id
++ * @data: packet data
++ * @datalen: length of data in bytes
++ */
++void mipv6_icmpv6_send(struct in6_addr *daddr, struct in6_addr *saddr, int type,
++                     int code, __u16 *id, __u16 flags, void *data, int datalen)
++{
++      struct sock *sk = mipv6_icmpv6_socket->sk;
++      struct flowi fl;
++      struct mipv6_icmpv6_msg msg;
++
++      DEBUG_FUNC();
++
++      fl.proto = IPPROTO_ICMPV6;
++      fl.fl6_dst = daddr;
++      fl.fl6_src = saddr;
++      fl.fl6_flowlabel = 0;
++      fl.uli_u.icmpt.type = type;
++      fl.uli_u.icmpt.code = code;
++
++      msg.icmph.icmp6_type = type;
++      msg.icmph.icmp6_code = code;
++      msg.icmph.icmp6_cksum = 0;
++
++      if (id)
++              msg.icmph.icmp6_identifier = htons(*id);
++      else
++              msg.icmph.icmp6_identifier = htons(identifier++);
++
++      msg.icmph.icmp6_sequence = htons(flags);
++      msg.data = data;
++      msg.csum = 0;
++      msg.len = datalen + sizeof(struct icmp6hdr);
++      msg.daddr = daddr;
++
++      if (mipv6_icmpv6_xmit_lock())
++              return;
++
++      ip6_build_xmit(sk, mipv6_icmpv6_getfrag, &msg, &fl, msg.len, NULL, -1,
++                     MSG_DONTWAIT);
++
++      ICMP6_INC_STATS_BH(Icmp6OutMsgs);
++      mipv6_icmpv6_xmit_unlock();
++}
++
++/**
++ * icmp6_rcv - ICMPv6 receive and multiplex
++ * @skb: buffer containing ICMP message
++ *
++ * Generic ICMPv6 receive function to multiplex messages to approriate
++ * handlers.  Only used for ICMP messages with special handling in
++ * Mobile IPv6.
++ **/
++static void icmp6_rcv(struct sk_buff *skb)
++{
++      struct icmp6hdr *hdr;
++
++      if (skb_is_nonlinear(skb) &&
++          skb_linearize(skb, GFP_ATOMIC) != 0) {
++              kfree_skb(skb);
++              return;
++      }
++      __skb_push(skb, skb->data-skb->h.raw);
++
++      hdr = (struct icmp6hdr *) skb->h.raw;
++
++      switch (hdr->icmp6_type) {
++      case ICMPV6_DEST_UNREACH:
++              mipv6_icmpv6_rcv_dest_unreach(skb);
++              break;
++
++      case ICMPV6_PARAMPROB:
++              mip6_fn.icmpv6_paramprob_rcv(skb);
++              break;
++
++      case MIPV6_DHAAD_REPLY:
++              mip6_fn.icmpv6_dhaad_rep_rcv(skb);
++              break;
++
++      case MIPV6_PREFIX_ADV:
++              mip6_fn.icmpv6_pfxadv_rcv(skb);
++              break;
++
++      case MIPV6_DHAAD_REQUEST:
++              mip6_fn.icmpv6_dhaad_req_rcv(skb);
++              break;
++
++      case MIPV6_PREFIX_SOLICIT:
++              mip6_fn.icmpv6_pfxsol_rcv(skb);
++              break;
++      }
++}
++
++int mipv6_icmpv6_init(void)
++{
++      struct sock *sk;
++      int err;
++
++      if ((mipv6_icmpv6_socket = sock_alloc()) == NULL) {
++              DEBUG(DBG_ERROR, "Cannot allocate mipv6_icmpv6_socket");
++              return -1;
++      }
++      mipv6_icmpv6_socket->type = SOCK_RAW;
++
++      if ((err = sock_create(PF_INET6, SOCK_RAW, IPPROTO_ICMP, 
++                             &mipv6_icmpv6_socket)) < 0) {
++              DEBUG(DBG_ERROR, "Cannot initialize mipv6_icmpv6_socket");
++              sock_release(mipv6_icmpv6_socket);
++              mipv6_icmpv6_socket = NULL; /* For safety */
++              return err;
++      }
++      sk = mipv6_icmpv6_socket->sk;
++      sk->allocation = GFP_ATOMIC;
++      sk->prot->unhash(sk);
++
++      /* Register our ICMP handler */
++      MIPV6_SETCALL(mipv6_icmp_rcv, icmp6_rcv);
++      return 0;
++}
++
++void mipv6_icmpv6_exit(void)
++{
++      MIPV6_RESETCALL(mipv6_icmp_rcv);
++      if (mipv6_icmpv6_socket)
++              sock_release(mipv6_icmpv6_socket);
++      mipv6_icmpv6_socket = NULL; /* For safety */
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp.h linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp.h  1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp.h      2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,43 @@
++/*
++ *      MIPL Mobile IPv6 ICMP send and receive prototypes
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _MIPV6_ICMP
++#define _MIPV6_ICMP
++
++#include <linux/config.h>
++#include <linux/in6.h>
++
++void mipv6_icmpv6_send(struct in6_addr *daddr, struct in6_addr *saddr,
++                     int type, int code, __u16 *id, __u16 flags,
++                     void *data, int datalen);
++
++void mipv6_icmpv6_send_dhaad_req(struct in6_addr *home_addr, int plen, __u16 dhaad_id);
++
++void mipv6_icmpv6_send_dhaad_rep(int ifindex, __u16 id, struct in6_addr *daddr);
++/* No handling */
++int mipv6_icmpv6_no_rcv(struct sk_buff *skb);
++
++/* Receive DHAAD Reply message */
++int mipv6_icmpv6_rcv_dhaad_rep(struct sk_buff *skb);
++/* Receive Parameter Problem message */
++int mipv6_icmpv6_rcv_paramprob(struct sk_buff *skb);
++/* Receive prefix advertisements */
++int mipv6_icmpv6_rcv_pfx_adv(struct sk_buff *skb);
++
++/* Receive DHAAD Request message */
++int mipv6_icmpv6_rcv_dhaad_req(struct sk_buff *skb);
++/* Receive prefix solicitations */
++int mipv6_icmpv6_rcv_pfx_sol(struct sk_buff *skb);
++
++int mipv6_icmpv6_init(void);
++void mipv6_icmpv6_exit(void);
++
++#endif
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp_ha.c linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp_ha.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp_ha.c       1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp_ha.c   2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,158 @@
++/*
++ *    Home Agent specific ICMP routines
++ *
++ *    Authors:
++ *    Antti Tuominen  <ajtuomin@tml.hut.fi>
++ *    Jaakko Laine    <medved@iki.fi>
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#include <linux/autoconf.h>
++#include <linux/sched.h>
++#include <net/ipv6.h>
++#include <net/addrconf.h>
++#include <net/ip6_route.h>
++#include <net/mipv6.h>
++
++#include "halist.h"
++#include "debug.h"
++#include "mipv6_icmp.h"
++//#include "prefix.h"
++
++/* Is this the easiest way of checking on 
++ *  which interface an anycast address is ?
++ */
++static int find_ac_dev(struct in6_addr *addr)
++{
++      int ifindex = 0;
++      struct net_device *dev;
++      read_lock(&dev_base_lock);
++      for (dev=dev_base; dev; dev=dev->next) {
++              if (ipv6_chk_acast_addr(dev, addr)) {
++                      ifindex = dev->ifindex;
++                      break;
++              }
++      }
++      read_unlock(&dev_base_lock);
++      return ifindex;
++}
++
++/**
++ * mipv6_icmpv6_send_dhaad_rep - Reply to DHAAD Request
++ * @ifindex: index of interface request was received from
++ * @id: request's identification number
++ * @daddr: requester's IPv6 address
++ *
++ * When Home Agent receives Dynamic Home Agent Address Discovery
++ * request, it replies with a list of home agents available on the
++ * home link.
++ */
++void mipv6_icmpv6_send_dhaad_rep(int ifindex, __u16 id, struct in6_addr *daddr)
++{
++      __u8 *data = NULL;
++      struct in6_addr home, *ha_addrs = NULL;
++      int addr_count, max_addrs, size = 0;
++
++      if (daddr == NULL)
++              return;
++
++      if (mipv6_ha_get_addr(ifindex, &home) < 0) {
++              DEBUG(DBG_INFO, "Not Home Agent in this interface");
++              return;
++      }
++
++      /* We send all available HA addresses, not exceeding a maximum
++       * number we can fit in a packet with minimum IPv6 MTU (to
++       * avoid fragmentation).
++       */
++      max_addrs = 76;
++      addr_count = mipv6_ha_get_pref_list(ifindex, &ha_addrs, max_addrs);
++
++      if (addr_count < 0) return;
++
++      if (addr_count != 0 && ha_addrs == NULL) {
++              DEBUG(DBG_ERROR, "addr_count = %d but return no addresses", 
++                    addr_count);
++              return;
++      }
++      data = (u8 *)ha_addrs;
++
++      size = addr_count * sizeof(struct in6_addr);
++
++      mipv6_icmpv6_send(daddr, &home, MIPV6_DHAAD_REPLY, 
++                        0, &id, 0, data, size);
++      if (ha_addrs) {
++              data = NULL;
++              kfree(ha_addrs);
++      }
++}
++
++/** 
++ * mipv6_icmpv6_dhaad_req - Home Agent Address Discovery Request ICMP handler
++ * @skb: buffer containing ICMP information message
++ *
++ * Special Mobile IPv6 ICMP message.  Handles Dynamic Home Agent
++ * Address Discovery Request messages.
++ **/
++int mipv6_icmpv6_rcv_dhaad_req(struct sk_buff *skb)
++{
++      struct icmp6hdr *phdr = (struct icmp6hdr *) skb->h.raw;
++      struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
++      struct in6_addr *daddr = &skb->nh.ipv6h->daddr;
++      __u16 identifier;
++      int ifindex = 0;
++
++      DEBUG_FUNC();
++
++      /* Invalid packet checks. */
++      if (phdr->icmp6_code != 0)
++              return 0;
++
++      identifier = ntohs(phdr->icmp6_identifier);
++
++      /* 
++       * Make sure we have the right ifindex (if the
++       * req came through another interface. 
++       */
++      ifindex = find_ac_dev(daddr);
++      if (ifindex == 0) { 
++              DEBUG(DBG_WARNING, "received dhaad request to anycast address %x:%x:%x:%x:%x:%x:%x:%x"
++                    " on which prefix we are not HA",
++                    NIPV6ADDR(daddr));
++              return 0;
++      }
++
++      /*
++       * send reply with list
++       */
++      mipv6_icmpv6_send_dhaad_rep(ifindex, identifier, saddr);
++      return 1;
++}
++#if 0
++/**
++ * mipv6_icmpv6_handle_pfx_sol - handle prefix solicitations
++ * @skb: sk_buff including the icmp6 message
++ */
++int mipv6_icmpv6_rcv_pfx_sol(struct sk_buff *skb)
++{
++      struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
++      struct in6_addr *daddr = &skb->nh.ipv6h->daddr;
++      struct inet6_ifaddr *ifp;
++
++      DEBUG_FUNC();
++
++      if (!(ifp = ipv6_get_ifaddr(daddr, NULL)))
++              return -1;
++
++      in6_ifa_put(ifp);
++      mipv6_pfx_cancel_send(saddr, -1);
++
++      return 0;
++}
++#endif
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp_mn.c linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp_mn.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/mipv6_icmp_mn.c       1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/mipv6_icmp_mn.c   2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,273 @@
++/*
++ *    Mobile Node specific ICMP routines
++ *
++ *    Authors:
++ *    Antti Tuominen  <ajtuomin@tml.hut.fi>
++ *    Jaakko Laine    <medved@iki.fi>
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#include <linux/sched.h>
++#include <net/ipv6.h>
++#include <net/ip6_route.h>
++#include <net/addrconf.h>
++#include <net/mipv6.h>
++
++#include "mn.h"
++#include "bul.h"
++#include "mdetect.h"
++#include "debug.h"
++#include "mipv6_icmp.h"
++#include "util.h"
++//#include "prefix.h"
++
++#define INFINITY 0xffffffff
++
++/**
++ * mipv6_icmpv6_paramprob - Parameter Problem ICMP error message handler
++ * @skb: buffer containing ICMP error message
++ *
++ * Special Mobile IPv6 ICMP handling.  If Mobile Node receives ICMP
++ * Parameter Problem message when using a Home Address Option,
++ * offending node should be logged and error message dropped.  If
++ * error is received because of a Binding Update, offending node
++ * should be recorded in Binding Update List and no more Binding
++ * Updates should be sent to this destination.  See RFC 3775 section
++ * 10.15.
++ **/
++int mipv6_icmpv6_rcv_paramprob(struct sk_buff *skb)
++{
++      struct icmp6hdr *phdr = (struct icmp6hdr *) skb->h.raw;
++      struct in6_addr *saddr = skb ? &skb->nh.ipv6h->saddr : NULL;
++      struct in6_addr *daddr = skb ? &skb->nh.ipv6h->daddr : NULL;
++      struct ipv6hdr *hdr = (struct ipv6hdr *) (phdr + 1);
++      int ulen = (skb->tail - (unsigned char *) (phdr + 1));
++
++      int errptr;
++      __u8 *off_octet;
++
++      DEBUG_FUNC();
++
++      /* We only handle code 1 & 2 messages. */
++      if (phdr->icmp6_code != ICMPV6_UNK_NEXTHDR &&
++          phdr->icmp6_code != ICMPV6_UNK_OPTION)
++              return 0;
++
++      /* Find offending octet in the original packet. */
++      errptr = ntohl(phdr->icmp6_pointer);
++
++      /* There is not enough of the original packet left to figure
++       * out what went wrong. Bail out. */
++      if (ulen <= errptr)
++              return 0;
++
++      off_octet = ((__u8 *) hdr + errptr);
++      DEBUG(DBG_INFO, "Parameter problem: offending octet %d [0x%2x]",
++            errptr, *off_octet);
++
++      /* If CN did not understand Mobility Header, set BUL entry to
++       * ACK_ERROR so no further BUs are sumbitted to this CN. */
++      if (phdr->icmp6_code == ICMPV6_UNK_NEXTHDR &&
++          *off_octet == IPPROTO_MOBILITY) {
++              struct bul_inval_args args;
++              args.all_rr_states = 1;
++              args.cn = saddr;
++              args.mn = daddr;
++              write_lock(&bul_lock);
++              mipv6_bul_iterate(mn_bul_invalidate, &args);
++              write_unlock(&bul_lock);
++      }
++
++      /* If CN did not understand Home Address Option, we log an
++       * error and discard the error message. */
++      if (phdr->icmp6_code == ICMPV6_UNK_OPTION &&
++          *off_octet == MIPV6_TLV_HOMEADDR) {
++              DEBUG(DBG_WARNING, "Correspondent node does not "
++                    "implement Home Address Option receipt.");
++              return 1;
++      }
++      return 0;
++}
++
++/**
++ * mipv6_mn_dhaad_send_req - Send DHAAD Request to home network
++ * @home_addr: address to do DHAAD for
++ * @plen: prefix length for @home_addr
++ *
++ * Send Dynamic Home Agent Address Discovery Request to the Home
++ * Agents anycast address in the nodes home network.
++ **/
++void 
++mipv6_icmpv6_send_dhaad_req(struct in6_addr *home_addr, int plen, __u16 dhaad_id)
++{
++      struct in6_addr ha_anycast;
++      struct in6_addr careofaddr;
++      
++      if (mipv6_get_care_of_address(home_addr, &careofaddr) < 0) {
++              DEBUG(DBG_WARNING, "Could not get node's Care-of Address");
++              return;
++      }
++
++      if (mipv6_ha_anycast(&ha_anycast, home_addr, plen) < 0) {
++              DEBUG(DBG_WARNING, 
++                    "Could not get Home Agent Anycast address for home address %x:%x.%x:%x:%x:%x:%x:%x/%d",
++                    NIPV6ADDR(home_addr), plen);
++              return;
++      }
++
++      mipv6_icmpv6_send(&ha_anycast, &careofaddr, MIPV6_DHAAD_REQUEST, 0, 
++                        &dhaad_id, 0, NULL, 0);
++
++}
++
++/** 
++ * mipv6_icmpv6_dhaad_rep - Home Agent Address Discovery Reply ICMP handler
++ * @skb: buffer containing ICMP information message
++ *
++ * Special Mobile IPv6 ICMP message.  Handles Dynamic Home Agent
++ * Address Discovery Reply messages.
++ **/
++int mipv6_icmpv6_rcv_dhaad_rep(struct sk_buff *skb)
++{
++      struct icmp6hdr *phdr = (struct icmp6hdr *) skb->h.raw;
++      struct in6_addr *address;
++      struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
++      __u16 identifier;
++      int ulen = (skb->tail - (unsigned char *) ((__u32 *) phdr + 2));
++      int i;
++      struct in6_addr home_addr, coa;
++      struct in6_addr *first_ha = NULL;
++      struct mn_info *minfo;
++      int n_addr = ulen / sizeof(struct in6_addr);
++
++      DEBUG_FUNC();
++
++      /* Invalid packet checks. */
++      if (ulen % sizeof(struct in6_addr) != 0)
++              return 0;
++
++      if (phdr->icmp6_code != 0)
++              return 0;
++
++      identifier = ntohs(phdr->icmp6_identifier);
++      if (ulen > 0) {
++              address = (struct in6_addr *) ((__u32 *) phdr + 2);
++      } else {
++              address = saddr;
++              n_addr = 1;
++      }
++
++      /* receive list of home agent addresses
++       * add to home agents list
++       */
++      DEBUG(DBG_INFO, "DHAAD: got %d home agents", n_addr);
++
++      first_ha = address;
++
++      /* lookup H@ with identifier */
++      read_lock(&mn_info_lock);
++      minfo = mipv6_mninfo_get_by_id(identifier);
++      if (!minfo) {
++              read_unlock(&mn_info_lock);
++              DEBUG(DBG_INFO, "no mninfo with id %d", 
++                    identifier);
++              return 0;
++      }
++      spin_lock(&minfo->lock);
++
++      /* Logic:
++       * 1. if old HA on list, prefer it
++       * 2. otherwise first HA on list prefered
++       */
++      for (i = 0; i < n_addr; i++) {
++              DEBUG(DBG_INFO, "HA[%d] %x:%x:%x:%x:%x:%x:%x:%x",
++                    i, NIPV6ADDR(address));
++              if (ipv6_addr_cmp(&minfo->ha, address) == 0) {
++                      spin_unlock(&minfo->lock);
++                      read_unlock(&mn_info_lock);
++                      return 0;
++              }
++              address++;
++      }
++      ipv6_addr_copy(&minfo->ha, first_ha);
++      spin_unlock(&minfo->lock);
++      ipv6_addr_copy(&home_addr, &minfo->home_addr);
++      read_unlock(&mn_info_lock);
++
++      mipv6_get_care_of_address(&home_addr, &coa);
++      init_home_registration(&home_addr, &coa);
++
++      return 1;
++}
++#if 0
++/**
++ * mipv6_icmpv6_handle_pfx_adv - handle prefix advertisements
++ * @skb: sk_buff including the icmp6 message
++ */
++int mipv6_icmpv6_rcv_pfx_adv(struct sk_buff *skb)
++{
++      struct icmp6hdr *hdr = (struct icmp6hdr *) skb->h.raw;
++      struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
++      struct in6_addr *daddr = &skb->nh.ipv6h->daddr;
++      __u8 *opt = (__u8 *) (hdr + 1);
++      int optlen = (skb->tail - opt);
++      unsigned long min_expire = INFINITY;
++      struct inet6_skb_parm *parm = (struct inet6_skb_parm *) skb->cb;
++
++      DEBUG_FUNC();
++
++      while (optlen > 0) {
++              int len = opt[1] << 3;
++              if (len == 0)
++                      goto set_timer;
++
++              if (opt[0] == ND_OPT_PREFIX_INFO) {
++                      int ifindex;
++                      unsigned long expire;
++                      struct prefix_info *pinfo =
++                              (struct prefix_info *) opt;
++                      struct net_device *dev;
++                      struct mn_info *mninfo;
++
++                      read_lock(&mn_info_lock);
++                      mninfo = mipv6_mninfo_get_by_ha(saddr);
++                      if (mninfo == NULL) {
++                              ifindex = 0;
++                      } else {
++                              spin_lock(&mninfo->lock);
++                              ifindex = mninfo->ifindex;
++                              spin_unlock(&mninfo->lock);
++                              mninfo = NULL;
++                      }
++                      read_unlock(&mn_info_lock);
++
++                      if (!(dev = dev_get_by_index(ifindex))) {
++                              DEBUG(DBG_WARNING, "Cannot find device by index %d", parm->iif);
++                              goto nextopt;
++                      }
++
++                      expire = ntohl(pinfo->valid);
++                      expire = expire == 0 ? INFINITY : expire;
++
++                      min_expire = expire < min_expire ? expire : min_expire;
++
++                      dev_put(dev);
++              }
++
++nextopt:
++              optlen -= len;
++              opt += len;
++      }
++
++set_timer:
++
++      mipv6_pfx_add_home(parm->iif, saddr, daddr, min_expire);
++      return 0;
++}
++#endif
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mn.c linux-2.4.25/net/ipv6/mobile_ip6/mn.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/mn.c  1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/mn.c      2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,1521 @@
++/*
++ *      Mobile-node functionality
++ *
++ *      Authors:
++ *      Sami Kivisaari          <skivisaa@cc.hut.fi>
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ *
++ */
++
++#include <linux/autoconf.h>
++#include <linux/sched.h>
++#include <linux/ipv6.h>
++#include <linux/net.h>
++#include <linux/init.h>
++#include <linux/skbuff.h>
++#include <linux/rtnetlink.h>
++#include <linux/if_arp.h>
++#include <linux/ipsec.h>
++#include <linux/notifier.h>
++#include <linux/list.h>
++#include <linux/route.h>
++#include <linux/netfilter.h>
++#include <linux/netfilter_ipv6.h>
++#include <linux/tqueue.h>
++#include <linux/proc_fs.h>
++
++#include <asm/uaccess.h>
++
++#include <net/ipv6.h>
++#include <net/addrconf.h>
++#include <net/neighbour.h>
++#include <net/ndisc.h>
++#include <net/ip6_route.h>
++#include <net/mipglue.h>
++
++#include "util.h"
++#include "mdetect.h"
++#include "bul.h"
++#include "mobhdr.h"
++#include "debug.h"
++#include "mn.h"
++#include "mipv6_icmp.h"
++#include "multiaccess_ctl.h"
++//#include "prefix.h"
++#include "tunnel_mn.h"
++#include "stats.h"
++#include "config.h"
++
++#define MIPV6_BUL_SIZE 128
++
++static LIST_HEAD(mn_info_list);
++
++/* Lock for list of MN infos */
++rwlock_t mn_info_lock = RW_LOCK_UNLOCKED;
++
++static spinlock_t ifrh_lock = SPIN_LOCK_UNLOCKED;
++
++struct ifr_holder {
++      struct list_head list;
++      struct in6_ifreq ifr;
++      int old_ifi;
++      struct handoff *ho;
++};
++
++LIST_HEAD(ifrh_list);
++
++static struct tq_struct mv_home_addr_task;
++
++/* Determines whether manually configured home addresses are preferred as 
++ * source addresses over dynamically configured ones
++ */
++int mipv6_use_preconfigured_hoaddr = 1; 
++
++/* Determines whether home addresses, which are at home are preferred as 
++ * source addresses over other home addresses
++ */
++int mipv6_use_topol_corr_hoaddr = 0;
++
++static spinlock_t icmpv6_id_lock = SPIN_LOCK_UNLOCKED;
++static __u16 icmpv6_id = 0;
++
++static inline __u16 mipv6_get_dhaad_id(void)
++{
++      __u16 ret;
++      spin_lock_bh(&icmpv6_id_lock);
++      ret = ++icmpv6_id;
++      spin_unlock_bh(&icmpv6_id_lock);
++      return ret;
++}
++
++/** 
++ * mipv6_mninfo_get_by_home - Returns mn_info for a home address
++ * @haddr: home address of MN
++ *
++ * Returns mn_info on success %NULL otherwise.  Caller MUST hold
++ * @mn_info_lock (read or write).
++ **/
++struct mn_info *mipv6_mninfo_get_by_home(struct in6_addr *haddr)
++{
++      struct list_head *lh;
++      struct mn_info *minfo;
++
++      DEBUG_FUNC();
++
++      if (!haddr)
++              return NULL;
++
++      list_for_each(lh, &mn_info_list) {
++              minfo = list_entry(lh, struct mn_info, list);
++              spin_lock(&minfo->lock);
++              if (!ipv6_addr_cmp(&minfo->home_addr, haddr)) {
++                      spin_unlock(&minfo->lock);
++                      return minfo;
++              }
++              spin_unlock(&minfo->lock);
++      }
++      return NULL;
++}
++
++/**
++ * mipv6_mninfo_get_by_ha - Lookup mn_info with Home Agent address
++ * @home_agent: Home Agent address
++ *
++ * Searches for a mn_info entry with @ha set to @home_agent.  You MUST
++ * hold @mn_info_lock when calling this function.  Returns pointer to
++ * mn_info entry or %NULL on failure.
++ **/
++struct mn_info *mipv6_mninfo_get_by_ha(struct in6_addr *home_agent)
++{
++      struct list_head *lh;
++      struct mn_info *minfo;
++
++      if (!home_agent)
++              return NULL;
++
++      list_for_each(lh, &mn_info_list) {
++              minfo = list_entry(lh, struct mn_info, list);
++              spin_lock(&minfo->lock);
++              if (!ipv6_addr_cmp(&minfo->ha, home_agent)) {
++                      spin_unlock(&minfo->lock);
++                      return minfo;
++              }
++              spin_unlock(&minfo->lock);
++      }
++      return NULL;
++}
++
++/**
++ * mipv6_mninfo_get_by_id - Lookup mn_info with id
++ * @id: DHAAD identifier
++ *
++ * Searches for a mn_info entry with @dhaad_id set to @id.  You MUST
++ * hold @mn_info_lock when calling this function.  Returns pointer to
++ * mn_info entry or %NULL on failure.
++ **/
++struct mn_info *mipv6_mninfo_get_by_id(unsigned short id)
++{
++      struct list_head *lh;
++      struct mn_info *minfo = 0;
++
++      list_for_each(lh, &mn_info_list) {
++              minfo = list_entry(lh, struct mn_info, list);
++              spin_lock(&minfo->lock);
++              if (minfo->dhaad_id == id) {
++                      spin_unlock(&minfo->lock);
++                      return minfo;
++              }
++              spin_unlock(&minfo->lock);
++      }
++      return NULL;
++}
++
++/** 
++ * mipv6_mninfo_add - Adds a new home info for MN
++ * @ifindex: Interface for home address
++ * @home_addr:  Home address of MN, must be set
++ * @plen: prefix length of the home address, must be set
++ * @isathome : home address at home
++ * @lifetime: lifetime of the home address, 0 is infinite
++ * @ha: home agent for the home address
++ * @ha_plen: prefix length of home agent's address, can be zero 
++ * @ha_lifetime: Lifetime of the home address, 0 is infinite
++ *
++ * The function adds a new home info entry for MN, allowing it to
++ * register the home address with the home agent.  Starts home
++ * registration process.  If @ha is %ADDRANY, DHAAD is performed to
++ * find a home agent.  Returns 0 on success, a negative value
++ * otherwise.  Caller MUST NOT hold @mn_info_lock or
++ * @addrconf_hash_lock.
++ **/
++void mipv6_mninfo_add(int ifindex, struct in6_addr *home_addr, int plen, 
++                    int isathome, unsigned long lifetime, struct in6_addr *ha, 
++                    int ha_plen, unsigned long ha_lifetime, int man_conf)
++{
++      struct mn_info *minfo;
++      struct in6_addr coa;
++
++      DEBUG_FUNC();
++
++      write_lock_bh(&mn_info_lock);
++      if ((minfo = mipv6_mninfo_get_by_home(home_addr)) != NULL){ 
++            DEBUG(1, "MN info already exists");
++            write_unlock_bh(&mn_info_lock);
++            return;
++      }
++      minfo = kmalloc(sizeof(struct mn_info), GFP_ATOMIC);
++      if (!minfo) {
++             write_unlock_bh(&mn_info_lock);
++             return;
++      }
++      memset(minfo, 0, sizeof(struct mn_info));
++      spin_lock_init(&minfo->lock);
++
++      
++      ipv6_addr_copy(&minfo->home_addr, home_addr);
++
++      if (ha)
++              ipv6_addr_copy(&minfo->ha, ha);
++      if (ha_plen < 128 && ha_plen > 0)
++              minfo->home_plen = ha_plen; 
++      else minfo->home_plen = 64;
++
++      minfo->ifindex_user = ifindex; /* Ifindex for tunnel interface */
++      minfo->ifindex = ifindex; /* Interface on which home address is currently conf'd */
++      /* TODO: we should get home address lifetime from somewhere */
++      /* minfo->home_addr_expires = jiffies + lifetime * HZ; */
++
++      /* manual configuration flag cannot be unset by dynamic updates 
++       *  from prefix advertisements
++       */
++      if (!minfo->man_conf) minfo->man_conf = man_conf; 
++      minfo->is_at_home = isathome;
++
++      list_add(&minfo->list, &mn_info_list);
++      write_unlock_bh(&mn_info_lock);
++
++      if (mipv6_get_care_of_address(home_addr, &coa) == 0) 
++              init_home_registration(home_addr, &coa);
++}
++
++/**
++ * mipv6_mninfo_del - Delete home info for MN 
++ * @home_addr : Home address or prefix 
++ * @del_dyn_only : Delete only dynamically created home entries 
++ *
++ * Deletes every mn_info entry that matches the first plen bits of
++ * @home_addr.  Returns number of deleted entries on success and a
++ * negative value otherwise.  Caller MUST NOT hold @mn_info_lock.
++ **/
++int mipv6_mninfo_del(struct in6_addr *home_addr, int del_dyn_only)
++{
++      struct list_head *lh, *next;
++      struct mn_info *minfo;
++      int ret = -1;
++      if (!home_addr)
++              return -1;
++
++      write_lock(&mn_info_lock);
++
++      list_for_each_safe(lh, next, &mn_info_list) {
++              minfo = list_entry(lh, struct mn_info, list);
++              if (ipv6_addr_cmp(&minfo->home_addr, home_addr) == 0
++                  && ((!minfo->man_conf && del_dyn_only) || !del_dyn_only)){
++                      list_del(&minfo->list);
++                      kfree(minfo);
++                      ret++;
++              }
++      }
++      write_unlock(&mn_info_lock);
++      return ret;
++}
++
++void mipv6_mn_set_home(int ifindex, struct in6_addr *homeaddr, int plen,
++                     struct in6_addr *homeagent, int ha_plen)
++{
++      mipv6_mninfo_add(ifindex, homeaddr, plen, 0, 0, 
++                       homeagent, ha_plen, 0, 1);
++}
++
++static int skip_dad(struct in6_addr *addr)
++{
++      struct mn_info *minfo;
++      int ret = 0;
++
++      if (addr == NULL) {
++              DEBUG(DBG_CRITICAL, "Null argument");
++              return 0;
++      }
++      read_lock_bh(&mn_info_lock);
++      if ((minfo = mipv6_mninfo_get_by_home(addr)) != NULL) {
++              if ((minfo->is_at_home != MN_NOT_AT_HOME) && (minfo->has_home_reg))
++                      ret = 1;
++              DEBUG(DBG_INFO, "minfo->is_at_home = %d, minfo->has_home_reg = %d",
++                    minfo->is_at_home, minfo->has_home_reg);
++      }
++      read_unlock_bh(&mn_info_lock);
++      
++      return ret;
++}
++/**
++ * mipv6_mn_is_home_addr - Determines if addr is node's home address
++ * @addr: IPv6 address
++ *
++ * Returns 1 if addr is node's home address.  Otherwise returns zero.
++ **/
++int mipv6_mn_is_home_addr(struct in6_addr *addr)
++{
++      int ret = 0;
++
++      if (addr == NULL) {
++              DEBUG(DBG_CRITICAL, "Null argument");
++              return -1;
++      }
++      read_lock_bh(&mn_info_lock);
++      if (mipv6_mninfo_get_by_home(addr))
++              ret = 1;
++      read_unlock_bh(&mn_info_lock);
++
++      return (ret);
++}
++
++/** 
++ * mipv6_mn_is_at_home - determine if node is home for a home address
++ * @home_addr : home address of MN
++ *
++ * Returns 1 if home address in question is in the home network, 0
++ * otherwise.  Caller MUST NOT not hold @mn_info_lock.
++ **/ 
++int mipv6_mn_is_at_home(struct in6_addr *home_addr)
++{
++      struct mn_info *minfo;
++      int ret = 0;
++      read_lock_bh(&mn_info_lock);
++      if ((minfo = mipv6_mninfo_get_by_home(home_addr)) != NULL) {
++              spin_lock(&minfo->lock);
++              ret = (minfo->is_at_home == MN_AT_HOME);
++              spin_unlock(&minfo->lock);
++      }
++      read_unlock_bh(&mn_info_lock);
++      return ret;
++}     
++void mipv6_mn_set_home_reg(struct in6_addr *home_addr, int has_home_reg)
++{
++      struct mn_info *minfo;
++      read_lock_bh(&mn_info_lock);
++
++      if ((minfo = mipv6_mninfo_get_by_home(home_addr)) != NULL) {
++              spin_lock(&minfo->lock);
++              minfo->has_home_reg = has_home_reg;
++              spin_unlock(&minfo->lock);
++      }
++      read_unlock_bh(&mn_info_lock);
++}     
++
++static int mn_inet6addr_event(
++      struct notifier_block *nb, unsigned long event, void *ptr)
++{
++      struct inet6_ifaddr *ifp = (struct inet6_ifaddr *)ptr;
++
++      switch (event) {
++      case NETDEV_UP:
++              /* Is address a valid coa ?*/
++              if (!(ifp->flags & IFA_F_TENTATIVE))
++                      mipv6_mdet_finalize_ho(&ifp->addr, 
++                                             ifp->idev->dev->ifindex);
++              else if(skip_dad(&ifp->addr))
++                      ifp->flags &= ~IFA_F_TENTATIVE;
++              break;
++      case NETDEV_DOWN:       
++#if 0
++              /* This is useless with manually configured home 
++                 addresses, which will not expire
++              */
++              mipv6_mninfo_del(&ifp->addr, 0);
++#endif
++        break;
++
++      }
++
++      return NOTIFY_DONE;
++}
++
++struct notifier_block mipv6_mn_inet6addr_notifier = {
++      mn_inet6addr_event,
++      NULL,
++      0 /* check if using zero is ok */
++};
++
++static void mipv6_get_saddr_hook(struct in6_addr *homeaddr)
++{
++      int found = 0, reiter = 0;
++      struct list_head *lh;
++      struct mn_info *minfo = NULL;
++      struct in6_addr coa;
++
++      read_lock_bh(&mn_info_lock);
++restart:
++      list_for_each(lh, &mn_info_list) {
++              minfo = list_entry(lh, struct mn_info, list);
++              if ((ipv6_addr_scope(homeaddr) != ipv6_addr_scope(&minfo->home_addr)) 
++                  || ipv6_chk_addr(&minfo->home_addr, NULL) == 0)
++                      continue; 
++
++              spin_lock(&minfo->lock);
++              if (minfo->is_at_home == MN_AT_HOME || minfo->has_home_reg) {
++                      if ((mipv6_use_topol_corr_hoaddr && 
++                           minfo->is_at_home == MN_AT_HOME) || 
++                          (mipv6_use_preconfigured_hoaddr && 
++                           minfo->man_conf) ||
++                          (!(mipv6_use_preconfigured_hoaddr || 
++                             mipv6_use_topol_corr_hoaddr) || reiter)) {
++                              spin_unlock(&minfo->lock);
++                              ipv6_addr_copy(homeaddr, &minfo->home_addr);
++                              found = 1;
++                              break;
++                      }
++              }
++              spin_unlock(&minfo->lock);
++      }
++      if (!found && !reiter) {
++              reiter = 1;
++              goto restart;
++      }
++
++      if (!found && minfo && 
++          !mipv6_get_care_of_address(&minfo->home_addr, &coa)) {
++              ipv6_addr_copy(homeaddr, &coa); 
++      }
++      read_unlock_bh(&mn_info_lock);
++
++      DEBUG(DBG_DATADUMP, "Source address selection:  %x:%x:%x:%x:%x:%x:%x:%x", 
++            NIPV6ADDR(homeaddr));
++      return;
++}
++
++static void mv_home_addr(void *arg)
++{
++      mm_segment_t oldfs;
++      int err = 0, new_if = 0;
++      struct list_head *lh, *next;
++      struct ifr_holder *ifrh;
++      LIST_HEAD(list);
++      
++      DEBUG(DBG_INFO, "mipv6 move home address task");
++
++      spin_lock_bh(&ifrh_lock);
++      list_splice_init(&ifrh_list, &list);
++      spin_unlock_bh(&ifrh_lock);
++      
++      oldfs = get_fs(); set_fs(KERNEL_DS);
++      list_for_each_safe(lh, next, &list) {
++              ifrh = list_entry(lh, struct ifr_holder, list);
++              if (ifrh->old_ifi) {
++                      new_if = ifrh->ifr.ifr6_ifindex;
++                      ifrh->ifr.ifr6_ifindex = ifrh->old_ifi;
++                      err = addrconf_del_ifaddr(&ifrh->ifr); 
++                      ifrh->ifr.ifr6_ifindex = new_if;
++                      if (err < 0)
++                              DEBUG(DBG_WARNING, "removal of home address %x:%x:%x:%x:%x:%x:%x:%x from" 
++                                    " old interface %d failed with status %d", 
++                                    NIPV6ADDR(&ifrh->ifr.ifr6_addr), ifrh->old_ifi, err);             
++              }
++              if(!err) {
++                      err = addrconf_add_ifaddr(&ifrh->ifr);
++              }
++              if (ifrh->ho) {
++                      DEBUG(DBG_INFO, "Calling mobile_node moved after moving home address to new if");
++                      mipv6_mobile_node_moved(ifrh->ho);
++              }
++              list_del(&ifrh->list);
++              kfree(ifrh);
++      }
++      set_fs(oldfs);
++
++      if (err < 0)
++              DEBUG(DBG_WARNING, "adding of home address to a new interface %d failed %d", new_if, err);
++      else {
++              DEBUG(DBG_WARNING, "adding of home address to a new interface OK");
++      }
++}
++
++struct dhaad_halist {
++      struct list_head list;
++      struct in6_addr addr;
++      int retry;
++};
++
++/* clear all has from candidate list.  do this when a new dhaad reply
++ * is received. */
++int mipv6_mn_flush_ha_candidate(struct list_head *ha)
++{
++      struct list_head *p, *tmp;
++      struct dhaad_halist *e;
++
++      list_for_each_safe(p, tmp, ha) {
++              e = list_entry(p, struct dhaad_halist, list);
++              list_del(p);
++              kfree(e);
++              e = NULL;
++      }
++      return 0;
++}
++
++/* add new ha to candidates. only done when dhaad reply is received. */
++int mipv6_mn_add_ha_candidate(struct list_head *ha, struct in6_addr *addr)
++{
++      struct dhaad_halist *e;
++
++      e = kmalloc(sizeof(*e), GFP_ATOMIC);
++      memset(e, 0, sizeof(*e));
++      ipv6_addr_copy(&e->addr, addr);
++
++      list_add_tail(&e->list, ha);
++      return 0;
++}
++
++#define MAX_RETRIES_PER_HA 3
++
++/* get next ha candidate.  this is done when dhaad reply has been
++ * received and we want to register with the best available ha. */
++int mipv6_mn_get_ha_candidate(struct list_head *ha, struct in6_addr *addr)
++{
++      struct list_head *p;
++
++      list_for_each(p, ha) {
++              struct dhaad_halist *e;
++              e = list_entry(p, typeof(*e), list);
++              if (e->retry >= 0 && e->retry < MAX_RETRIES_PER_HA) {
++                      ipv6_addr_copy(addr, &e->addr);
++                      return 0;
++              }
++      }
++      return -1;
++}
++
++/* change candidate status.  if registration with ha fails, we
++ * increase retry for ha candidate.  if retry is >= 3 we set it to -1
++ * (failed), do get_ha_candidate() again */
++int mipv6_mn_try_ha_candidate(struct list_head *ha, struct in6_addr *addr)
++{
++      struct list_head *p;
++
++      list_for_each(p, ha) {
++              struct dhaad_halist *e;
++              e = list_entry(p, typeof(*e), list);
++              if (ipv6_addr_cmp(addr, &e->addr) == 0) {
++                      if (e->retry >= MAX_RETRIES_PER_HA) e->retry = -1;
++                      else if (e->retry >= 0) e->retry++;
++                      return 0;
++              }
++      }
++      return -1;
++}
++
++/**
++ * mipv6_mn_get_bulifetime - Get lifetime for a binding update
++ * @home_addr: home address for BU 
++ * @coa: care-of address for BU
++ * @flags: flags used for BU 
++ *
++ * Returns maximum lifetime for BUs determined by the lifetime of
++ * care-of address and the lifetime of home address.
++ **/
++__u32 mipv6_mn_get_bulifetime(struct in6_addr *home_addr, struct in6_addr *coa,
++                            __u8 flags)
++{
++      struct inet6_ifaddr *ifp_hoa, *ifp_coa;
++      __u32 lifetime = (flags & MIPV6_BU_F_HOME ? 
++                        HA_BU_DEF_LIFETIME : CN_BU_DEF_LIFETIME); 
++
++      ifp_hoa = ipv6_get_ifaddr(home_addr, NULL);
++      if(!ifp_hoa) {
++              DEBUG(DBG_INFO, "home address missing");
++              return 0;
++      }
++      if (!(ifp_hoa->flags & IFA_F_PERMANENT)){
++              if (ifp_hoa->valid_lft)
++                      lifetime = min_t(__u32, lifetime, ifp_hoa->valid_lft);
++              else
++                      DEBUG(DBG_ERROR, "Zero lifetime for home address");
++      }
++      in6_ifa_put(ifp_hoa);
++
++      ifp_coa = ipv6_get_ifaddr(coa, NULL);
++      if (!ifp_coa) { 
++              DEBUG(DBG_INFO, "care-of address missing");
++              return 0;
++      }
++      if (!(ifp_coa->flags & IFA_F_PERMANENT)) {
++              if(ifp_coa->valid_lft)
++                      lifetime = min_t(__u32, lifetime, ifp_coa->valid_lft);
++              else
++                      DEBUG(DBG_ERROR, 
++                            "Zero lifetime for care-of address");
++      }
++      in6_ifa_put(ifp_coa);
++
++      DEBUG(DBG_INFO, "Lifetime for binding is %ld", lifetime);
++      return lifetime;
++}
++
++static int 
++mipv6_mn_tnl_rcv_send_bu_hook(struct ip6_tnl *t, struct sk_buff *skb)
++{
++      struct ipv6hdr *inner;
++      struct ipv6hdr *outer = skb->nh.ipv6h; 
++      struct mn_info *minfo = NULL;
++      __u32 lifetime;
++      __u8 user_flags = 0;
++
++      DEBUG_FUNC();
++
++      if (!is_mip6_tnl(t))
++              return IP6_TNL_ACCEPT;
++
++      if (!mip6node_cnf.accept_ret_rout) {
++              DEBUG(DBG_INFO, "Return routability administratively disabled" 
++                    " not doing route optimization");
++              return IP6_TNL_ACCEPT;
++      }
++      if (!pskb_may_pull(skb, skb->h.raw-skb->data+sizeof(*inner)))
++              return IP6_TNL_DROP;
++
++      inner = (struct ipv6hdr *)skb->h.raw;
++      
++      read_lock(&mn_info_lock);
++      minfo = mipv6_mninfo_get_by_home(&inner->daddr);
++
++      if (!minfo) {
++              DEBUG(DBG_WARNING, "MN info missing");
++              read_unlock(&mn_info_lock);
++              return IP6_TNL_ACCEPT;
++      }
++      DEBUG(DBG_DATADUMP, "MIPV6 MN: Received a tunneled IPv6 packet"
++            " to %x:%x:%x:%x:%x:%x:%x:%x,"
++            " from %x:%x:%x:%x:%x:%x:%x:%x with\n tunnel header"
++            "daddr: %x:%x:%x:%x:%x:%x:%x:%x,"
++            "saddr: %x:%x:%x:%x:%x:%x:%x:%x", 
++             NIPV6ADDR(&inner->daddr), NIPV6ADDR(&inner->saddr),
++             NIPV6ADDR(&outer->daddr), NIPV6ADDR(&outer->saddr));
++      
++      spin_lock(&minfo->lock);
++
++      /* We don't send bus in response to all tunneled packets */
++
++        if (!ipv6_addr_cmp(&minfo->ha, &inner->saddr)) {
++              spin_unlock(&minfo->lock);
++              read_unlock(&mn_info_lock);
++                DEBUG(DBG_ERROR, "HA BUG: Received a tunneled packet "
++                    "originally sent by home agent, not sending BU");
++              return IP6_TNL_ACCEPT;
++        }
++      spin_unlock(&minfo->lock);
++      read_unlock(&mn_info_lock);
++
++      DEBUG(DBG_DATADUMP, "Sending BU to correspondent node");
++
++      user_flags |= mip6node_cnf.bu_cn_ack ? MIPV6_BU_F_ACK : 0;
++
++      if (inner->nexthdr != IPPROTO_DSTOPTS && 
++          inner->nexthdr != IPPROTO_MOBILITY) {
++              struct in6_addr coa;
++              /* Don't start RR when receiving ICMP error messages */
++              if (inner->nexthdr == IPPROTO_ICMPV6) {
++                      int ptr = (u8*)(inner+1) - skb->data;
++                      u8 type;
++
++                      if (skb_copy_bits(skb,
++                                        ptr+offsetof(struct icmp6hdr,
++                                                     icmp6_type),
++                                        &type, 1)
++                          || !(type & ICMPV6_INFOMSG_MASK)) {
++                              return IP6_TNL_ACCEPT;
++                      }
++              }
++              lifetime = mipv6_mn_get_bulifetime(&inner->daddr,
++                                                 &outer->daddr, 0); 
++              if (lifetime && 
++                  !mipv6_get_care_of_address(&inner->daddr, &coa)) {
++                      write_lock(&bul_lock);
++                      mipv6_send_bu(&inner->daddr, &inner->saddr, &coa,
++                                    INITIAL_BINDACK_TIMEOUT,
++                                    MAX_BINDACK_TIMEOUT, 1, 
++                                    user_flags,
++                                    lifetime, NULL);
++                      write_unlock(&bul_lock);
++              }
++      }
++      DEBUG(DBG_DATADUMP, "setting rcv_tunnel flag in skb");
++      skb->security |= MIPV6_RCV_TUNNEL;
++      return IP6_TNL_ACCEPT;
++}
++
++static struct ip6_tnl_hook_ops mipv6_mn_tnl_rcv_send_bu_ops = {
++      {NULL, NULL}, 
++      IP6_TNL_PRE_DECAP,
++      IP6_TNL_PRI_FIRST,
++      mipv6_mn_tnl_rcv_send_bu_hook
++};
++
++static int
++mipv6_mn_tnl_xmit_stats_hook(struct ip6_tnl *t, struct sk_buff *skb)
++{
++      DEBUG_FUNC();
++      if (is_mip6_tnl(t))
++              MIPV6_INC_STATS(n_encapsulations);
++      return IP6_TNL_ACCEPT;
++}
++
++static struct ip6_tnl_hook_ops mipv6_mn_tnl_xmit_stats_ops = {
++      {NULL, NULL},
++      IP6_TNL_PRE_ENCAP,
++      IP6_TNL_PRI_LAST,
++      mipv6_mn_tnl_xmit_stats_hook
++};
++
++static int
++mipv6_mn_tnl_rcv_stats_hook(struct ip6_tnl *t, struct sk_buff *skb)
++{
++      DEBUG_FUNC();   
++      if (is_mip6_tnl(t))
++              MIPV6_INC_STATS(n_decapsulations);
++      return IP6_TNL_ACCEPT;
++}
++
++static struct ip6_tnl_hook_ops mipv6_mn_tnl_rcv_stats_ops = {
++      {NULL, NULL},
++      IP6_TNL_PRE_DECAP,
++      IP6_TNL_PRI_LAST,
++      mipv6_mn_tnl_rcv_stats_hook
++};
++
++static void mn_check_tunneled_packet(struct sk_buff *skb)
++{
++      DEBUG_FUNC();
++      /* If tunnel flag was set */
++      if (skb->security & MIPV6_RCV_TUNNEL) {
++              struct in6_addr coa; 
++              __u32 lifetime;
++              __u8 user_flags = 0;
++              int ptr = (u8*)(skb->nh.ipv6h+1) - skb->data;
++              int len = skb->len - ptr;
++              __u8 nexthdr = skb->nh.ipv6h->nexthdr;
++
++              if (len < 0)
++                      return;
++
++              ptr = ipv6_skip_exthdr(skb, ptr, &nexthdr, len);
++              if (ptr < 0)
++                      return;
++
++              if (!mip6node_cnf.accept_ret_rout) {
++                      DEBUG(DBG_INFO, "Return routability administratively disabled");
++                      return;
++              }
++              if (nexthdr == IPPROTO_MOBILITY)
++                      return;
++            
++              /* Don't start RR when receiving ICMP error messages */
++              if (nexthdr == IPPROTO_ICMPV6) {
++                      u8 type;
++
++                      if (skb_copy_bits(skb,
++                                        ptr+offsetof(struct icmp6hdr,
++                                                     icmp6_type),
++                                        &type, 1)
++                          || !(type & ICMPV6_INFOMSG_MASK)) {
++                              return;
++                      }
++              }
++              user_flags |= mip6node_cnf.bu_cn_ack ? MIPV6_BU_F_ACK : 0;
++              mipv6_get_care_of_address(&skb->nh.ipv6h->daddr, &coa);
++              lifetime = mipv6_mn_get_bulifetime(&skb->nh.ipv6h->daddr,
++                                                       &coa, 0); 
++
++              DEBUG(DBG_WARNING, "packet to address %x:%x:%x:%x:%x:%x:%x:%x"
++                    "was tunneled. Sending BU to CN" 
++                    "%x:%x:%x:%x:%x:%x:%x:%x", 
++                    NIPV6ADDR(&skb->nh.ipv6h->daddr),
++                    NIPV6ADDR(&skb->nh.ipv6h->saddr)); 
++              /* This should work also with home address option */
++              
++              write_lock(&bul_lock);
++              mipv6_send_bu(&skb->nh.ipv6h->daddr, &skb->nh.ipv6h->saddr, 
++                            &coa, INITIAL_BINDACK_TIMEOUT,
++                            MAX_BINDACK_TIMEOUT, 1, user_flags,
++                            lifetime, NULL);
++              write_unlock(&bul_lock);
++      }
++}
++
++static int sched_mv_home_addr_task(struct in6_addr *haddr, int plen_new, 
++                                 int newif, int oldif, struct handoff *ho)
++{
++      int alloc_size;
++      struct ifr_holder *ifrh;
++
++      alloc_size = sizeof(*ifrh) + (ho ? sizeof(*ho): 0);
++      if ((ifrh = kmalloc(alloc_size, GFP_ATOMIC)) == NULL) {
++              DEBUG(DBG_ERROR, "Out of memory");
++              return -1;
++      } 
++      if (ho) {
++              ifrh->ho = (struct handoff *)((struct ifr_holder *)(ifrh + 1));
++              memcpy(ifrh->ho, ho, sizeof(*ho));
++      } else 
++              ifrh->ho = NULL;
++
++      /* must queue task to avoid deadlock with rtnl */
++      ifrh->ifr.ifr6_ifindex = newif;
++      ifrh->ifr.ifr6_prefixlen = plen_new;
++      ipv6_addr_copy(&ifrh->ifr.ifr6_addr, haddr);
++      ifrh->old_ifi = oldif;
++      
++      spin_lock_bh(&ifrh_lock);
++      list_add_tail(&ifrh->list, &ifrh_list);
++      spin_unlock_bh(&ifrh_lock);
++
++      schedule_task(&mv_home_addr_task);
++
++      return 0;
++}
++
++static void send_ret_home_ns(struct in6_addr *ha_addr, 
++                           struct in6_addr *home_addr,
++                           int ifindex)
++{
++      struct in6_addr nil;
++      struct in6_addr mcaddr;
++      struct net_device *dev = dev_get_by_index(ifindex);
++      if (!dev)
++              return;
++      memset(&nil, 0, sizeof(nil));
++      addrconf_addr_solict_mult(home_addr, &mcaddr);
++      ndisc_send_ns(dev, NULL, home_addr, &mcaddr, &nil); 
++      dev_put(dev);
++}
++
++static inline int ha_is_reachable(int ifindex, struct in6_addr *ha)
++{
++      struct net_device *dev;
++      int reachable = 0;
++
++      dev = dev_get_by_index(ifindex);
++      if (dev) {
++              struct neighbour *neigh;
++              if ((neigh = ndisc_get_neigh(dev, ha)) != NULL) {
++                      read_lock_bh(&neigh->lock);
++                      if (neigh->nud_state&NUD_VALID)
++                              reachable = 1;
++                      read_unlock_bh(&neigh->lock);
++                      neigh_release(neigh);
++              }
++              dev_put(dev);
++      }
++      return reachable;
++}
++
++static int mn_ha_handoff(struct handoff *ho)
++{
++      struct list_head *lh;
++      struct mn_info *minfo;
++      struct in6_addr *coa= ho->coa;
++      int wait_mv_home = 0; 
++
++      read_lock_bh(&mn_info_lock);
++      list_for_each(lh, &mn_info_list) {
++              __u8 has_home_reg;
++              int ifindex;
++              struct in6_addr ha;
++              __u8 athome;
++              __u32 lifetime;
++              struct mipv6_bul_entry *entry = NULL;
++              
++              minfo = list_entry(lh, struct mn_info, list);
++              spin_lock(&minfo->lock);
++              has_home_reg = minfo->has_home_reg;
++              ifindex = minfo->ifindex;
++              ipv6_addr_copy(&ha, &minfo->ha);
++              
++              if (mipv6_prefix_compare(&ho->rtr_addr, &minfo->home_addr,
++                                       ho->plen)) {
++                      if (minfo->has_home_reg)
++                              athome = minfo->is_at_home = MN_RETURNING_HOME;
++                      else
++                              athome = minfo->is_at_home = MN_AT_HOME;
++                      coa = &minfo->home_addr;
++
++                      spin_unlock(&minfo->lock);
++#if 0                 
++                      /* Cancel prefix solicitation, rtr is our HA */
++                      mipv6_pfx_cancel_send(&ho->rtr_addr, ifindex);
++#endif                        
++                      minfo->ifindex = ho->ifindex;
++
++                      if (minfo->has_home_reg && 
++                          !ha_is_reachable(ho->ifindex, &minfo->ha)) {
++                              send_ret_home_ns(&minfo->ha,
++                                               &minfo->home_addr, 
++                                               ho->ifindex);
++                              mipv6_mdet_set_curr_rtr_reachable(0);
++                              wait_mv_home++;
++                      }
++                      if (ifindex != ho->ifindex){
++                              wait_mv_home++;
++                              DEBUG(DBG_INFO, 
++                                    "Moving home address back to "
++                                    "the home interface");
++                              sched_mv_home_addr_task(&minfo->home_addr, 
++                                                      128,
++                                                      ho->ifindex, 
++                                                      ifindex, ho);
++                      }
++                      if (!has_home_reg || wait_mv_home)
++                              continue;
++                      
++                      lifetime = 0;
++
++              } else {
++                      athome = minfo->is_at_home = MN_NOT_AT_HOME;
++                      if (minfo->ifindex_user != minfo->ifindex) {
++                              DEBUG(DBG_INFO, "Scheduling home address move to virtual interface");
++                              sched_mv_home_addr_task(&minfo->home_addr, 
++                                                      128,
++                                                      minfo->ifindex_user, 
++                                                      minfo->ifindex, ho); /* Is minfo->ifindex correct */
++                              
++                              wait_mv_home++;
++                      }
++                      minfo->ifindex = minfo->ifindex_user;
++                      spin_unlock(&minfo->lock);
++                      if (wait_mv_home)
++                              continue;
++                      if (!has_home_reg &&
++                          init_home_registration(&minfo->home_addr, 
++                                                 ho->coa)) {
++                              continue;
++                      }
++                      lifetime = mipv6_mn_get_bulifetime(&minfo->home_addr, 
++                                                         ho->coa,
++                                                         MIPV6_BU_F_HOME);
++                      
++              }
++              write_lock(&bul_lock);
++              if (!(entry = mipv6_bul_get(&ha, &minfo->home_addr)) ||
++                  !(entry->flags & MIPV6_BU_F_HOME)) {
++                      DEBUG(DBG_ERROR, 
++                            "Unable to find home registration for "
++                            "home address: %x:%x:%x:%x:%x:%x:%x:%x!\n",
++                            NIPV6ADDR(&minfo->home_addr));
++                      write_unlock(&bul_lock);
++                      continue;
++              }
++              DEBUG(DBG_INFO, "Sending home de ? %d registration for "
++                    "home address: %x:%x:%x:%x:%x:%x:%x:%x\n" 
++                    "to home agent %x:%x:%x:%x:%x:%x:%x:%x, "
++                    "with lifetime %ld", 
++                    (athome != MN_NOT_AT_HOME),  
++                    NIPV6ADDR(&entry->home_addr), 
++                    NIPV6ADDR(&entry->cn_addr), lifetime);
++              mipv6_send_bu(&entry->home_addr, &entry->cn_addr, 
++                            coa, INITIAL_BINDACK_TIMEOUT, 
++                            MAX_BINDACK_TIMEOUT, 1, entry->flags, 
++                            lifetime, NULL);
++              write_unlock(&bul_lock);
++
++      }
++      read_unlock_bh(&mn_info_lock);
++      return wait_mv_home;
++}
++/**
++ * mn_cn_handoff - called for every bul entry to send BU to CN
++ * @rawentry: bul entry
++ * @args: handoff event
++ * @sortkey:
++ *
++ * Since MN can have many home addresses and home networks, every BUL
++ * entry needs to be checked
++ **/
++int mn_cn_handoff(void *rawentry, void *args, unsigned long *sortkey)
++{
++      struct mipv6_bul_entry *entry = (struct mipv6_bul_entry *)rawentry;
++      struct in6_addr *coa = (struct in6_addr *)args;
++
++      DEBUG_FUNC();
++
++      /* Home registrations already handled by mn_ha_handoff */
++      if (entry->flags & MIPV6_BU_F_HOME)
++              return ITERATOR_CONT;
++
++      /* BUL is locked by mipv6_mobile_node_moved which calls us 
++         through mipv6_bul_iterate */
++
++      if (mipv6_prefix_compare(coa, 
++                               &entry->home_addr,
++                               64)) {
++              mipv6_send_bu(&entry->home_addr, &entry->cn_addr, 
++                            &entry->home_addr, INITIAL_BINDACK_TIMEOUT, 
++                            MAX_BINDACK_TIMEOUT, 1, entry->flags, 0, 
++                            NULL);
++      } else {
++              u32 lifetime = mipv6_mn_get_bulifetime(&entry->home_addr, 
++                                                     coa,
++                                                     entry->flags);
++              mipv6_send_bu(&entry->home_addr, &entry->cn_addr,
++                              coa, INITIAL_BINDACK_TIMEOUT,
++                            MAX_BINDACK_TIMEOUT, 1, entry->flags,
++                            lifetime, NULL);
++      }
++      return ITERATOR_CONT;
++}
++
++
++int mn_bul_invalidate(void *rawentry, void *args, unsigned long *sortkey)
++{
++      struct mipv6_bul_entry *bul = (struct mipv6_bul_entry *)rawentry;
++      struct bul_inval_args *arg = (struct bul_inval_args *)args;
++
++      DEBUG_FUNC();
++
++      if (!ipv6_addr_cmp(arg->cn, &bul->cn_addr) &&
++          (!ipv6_addr_cmp(arg->mn, &bul->home_addr) ||
++           !ipv6_addr_cmp(arg->mn, &bul->coa))) {
++              if (arg->all_rr_states || !bul->rr ||
++                  (bul->rr->rr_state != RR_INIT && 
++                   bul->rr->rr_state != RR_DONE)) {
++                      bul->state = ACK_ERROR;
++                      bul->callback = bul_entry_expired;
++                      bul->callback_time = jiffies +
++                              DUMB_CN_BU_LIFETIME * HZ;
++                      bul->expire = bul->callback_time;
++                      DEBUG(DBG_INFO, "BUL entry set to ACK_ERROR");
++                      mipv6_bul_reschedule(bul);
++              }
++      }
++      return ITERATOR_CONT;
++}
++/**
++ * init_home_registration - start Home Registration process
++ * @home_addr: home address
++ * @coa: care-of address
++ *
++ * Checks whether we have a Home Agent address for this home address.
++ * If not starts Dynamic Home Agent Address Discovery.  Otherwise
++ * tries to register with home agent if not already registered.
++ * Returns 1, if home registration process is started and 0 otherwise
++ **/
++int init_home_registration(struct in6_addr *home_addr, struct in6_addr *coa)
++{
++      struct mn_info *hinfo;
++      struct in6_addr ha;
++      __u8 man_conf;
++      int ifindex;
++      __u32 lifetime;
++      __u8 user_flags = 0, flags;
++
++      DEBUG_FUNC();
++
++      read_lock_bh(&mn_info_lock);
++        if ((hinfo = mipv6_mninfo_get_by_home(home_addr)) == NULL) {
++                DEBUG(DBG_ERROR, "No mn_info found for address: "
++                    "%x:%x:%x:%x:%x:%x:%x:%x",
++                    NIPV6ADDR(home_addr));
++              read_unlock_bh(&mn_info_lock);
++                return -ENOENT;
++        }
++      spin_lock(&hinfo->lock);
++      if (mipv6_prefix_compare(&hinfo->home_addr, coa, hinfo->home_plen)) { 
++              spin_unlock(&hinfo->lock);
++              read_unlock_bh(&mn_info_lock);
++              DEBUG(DBG_INFO, "Adding home address, MN at home");
++              return 1;
++      }
++        if (ipv6_addr_any(&hinfo->ha)) {
++                int dhaad_id = mipv6_get_dhaad_id();
++                hinfo->dhaad_id = dhaad_id;
++              spin_unlock(&hinfo->lock);
++                mipv6_icmpv6_send_dhaad_req(home_addr, hinfo->home_plen, dhaad_id);
++              read_unlock_bh(&mn_info_lock);
++                DEBUG(DBG_INFO,
++                    "Home Agent address not set, initiating DHAAD");
++                return 1;
++        }
++        ipv6_addr_copy(&ha, &hinfo->ha);
++        man_conf = hinfo->man_conf;
++        ifindex = hinfo->ifindex;
++      spin_unlock(&hinfo->lock);
++      read_unlock_bh(&mn_info_lock);
++#if 0 
++      if (man_conf)
++              mipv6_pfx_add_ha(&ha, coa, ifindex);
++#endif        
++      if (mipv6_bul_exists(&ha, home_addr)) {
++              DEBUG(DBG_INFO, "BU already sent to HA");
++              return 0;
++      }
++      /* user flags received through sysctl */
++      user_flags |= mip6node_cnf.bu_lladdr ? MIPV6_BU_F_LLADDR : 0;
++      user_flags |= mip6node_cnf.bu_keymgm ? MIPV6_BU_F_KEYMGM : 0;
++
++      flags = MIPV6_BU_F_HOME | MIPV6_BU_F_ACK | user_flags;
++
++      lifetime = mipv6_mn_get_bulifetime(home_addr, coa, flags);
++
++      DEBUG(DBG_INFO, "Sending initial home registration for "
++            "home address: %x:%x:%x:%x:%x:%x:%x:%x\n" 
++            "to home agent %x:%x:%x:%x:%x:%x:%x:%x, "
++            "with lifetime %ld, prefixlength %d",   
++            NIPV6ADDR(home_addr), NIPV6ADDR(&ha), lifetime, 0);
++
++      write_lock_bh(&bul_lock);
++      mipv6_send_bu(home_addr, &ha, coa, INITIAL_BINDACK_DAD_TIMEOUT,
++                    MAX_BINDACK_TIMEOUT, 1, flags, lifetime, NULL);
++      write_unlock_bh(&bul_lock);
++
++      return 1;
++}
++
++/**
++ * mipv6_mobile_node_moved - Send BUs to all HAs and CNs
++ * @ho: handoff structure contains the new and previous routers
++ *
++ * Event for handoff.  Sends BUs everyone on Binding Update List.
++ **/
++int mipv6_mobile_node_moved(struct handoff *ho)
++{
++#if 0
++      int bu_to_prev_router = 1;
++#endif
++      int dummy;
++
++      DEBUG_FUNC();
++
++      ma_ctl_upd_iface(ho->ifindex, 
++                       MA_IFACE_CURRENT | MA_IFACE_HAS_ROUTER, &dummy);
++
++      /* First send BU to HA, then to all other nodes that are on BU list */
++      if (mn_ha_handoff(ho) != 0)
++              return 0; /* Wait for move home address task */
++#if 0  
++      /* Add current care-of address to mn_info list, if current router acts 
++         as a HA.*/ 
++
++      if (ho->home_address && bu_to_prev_router) 
++              mipv6_mninfo_add(ho->coa, ho->plen, 
++                               MN_AT_HOME, 0, &ho->rtr_addr, 
++                               ho->plen, ROUTER_BU_DEF_LIFETIME,
++                               0);
++                                
++#endif
++      return 0;               
++}
++
++/**
++ * mipv6_mn_send_home_na - send NA when returning home
++ * @haddr: home address to advertise
++ *
++ * After returning home, MN must advertise all its valid addresses in
++ * home link to all nodes.
++ **/
++void mipv6_mn_send_home_na(struct in6_addr *haddr)
++{
++      struct net_device *dev = NULL;
++      struct in6_addr mc_allnodes;
++      struct mn_info *hinfo = NULL;
++ 
++      read_lock(&mn_info_lock);
++      hinfo = mipv6_mninfo_get_by_home(haddr);
++      if (!hinfo) {
++              read_unlock(&mn_info_lock);
++              return;
++      }
++      spin_lock(&hinfo->lock);
++      hinfo->is_at_home = MN_AT_HOME;
++      dev = dev_get_by_index(hinfo->ifindex);
++      spin_unlock(&hinfo->lock);
++      read_unlock(&mn_info_lock);
++      if (dev == NULL) {
++              DEBUG(DBG_ERROR, "Send home_na: device not found.");
++              return;
++      }
++      
++      ipv6_addr_all_nodes(&mc_allnodes);
++      ndisc_send_na(dev, NULL, &mc_allnodes, haddr, 0, 0, 1, 1);
++      dev_put(dev);
++}
++
++static int mn_use_hao(struct in6_addr *daddr, struct in6_addr *saddr)
++{
++      struct mipv6_bul_entry *entry;
++      struct mn_info *minfo = NULL;
++      int add_ha = 0;
++
++      read_lock_bh(&mn_info_lock);
++      minfo = mipv6_mninfo_get_by_home(saddr);
++      if (minfo && minfo->is_at_home != MN_AT_HOME) {
++              read_lock_bh(&bul_lock);
++              if ((entry = mipv6_bul_get(daddr, saddr)) == NULL) {
++                      read_unlock_bh(&bul_lock);
++                      read_unlock_bh(&mn_info_lock);
++                      return add_ha;
++              }
++              add_ha = (entry->state != ACK_ERROR &&
++                        (!entry->rr || entry->rr->rr_state == RR_DONE || 
++                         entry->flags & MIPV6_BU_F_HOME));
++              read_unlock_bh(&bul_lock);
++      }
++      read_unlock_bh(&mn_info_lock);
++      return add_ha;
++}
++
++static int 
++mn_dev_event(struct notifier_block *nb, unsigned long event, void *ptr)
++{
++      struct net_device *dev = ptr;
++      struct list_head *lh;
++      struct mn_info *minfo;
++      int newif = 0;
++
++      /* here are probably the events we need to worry about */
++      switch (event) {
++      case NETDEV_UP:
++              DEBUG(DBG_DATADUMP, "New netdevice %s registered.", dev->name);
++              if (dev->type != ARPHRD_LOOPBACK && !dev_is_mip6_tnl(dev)) 
++                      ma_ctl_add_iface(dev->ifindex);
++                      
++              break;
++      case NETDEV_GOING_DOWN:
++              DEBUG(DBG_DATADUMP, "Netdevice %s disappeared.", dev->name);
++              /* 
++               * Go through mn_info list and move all home addresses on the
++               * netdev going down to a new device. This will make it 
++                 * practically impossible for the home address to return home,
++               * but allow MN to retain its connections using the address.
++               */
++
++              read_lock_bh(&mn_info_lock);
++              list_for_each(lh, &mn_info_list) {
++                      minfo = list_entry(lh, struct mn_info, list);
++                      spin_lock(&minfo->lock);
++                      if (minfo->ifindex == dev->ifindex) {
++                              if (sched_mv_home_addr_task(&minfo->home_addr, 128, 
++                                                          minfo->ifindex_user, 
++                                                          0, NULL) < 0) {
++                                      minfo->ifindex = 0;
++                                      spin_unlock(&minfo->lock);
++                                      read_unlock_bh(&mn_info_lock);
++                                      return NOTIFY_DONE;
++                              } else { 
++                                      minfo->ifindex = minfo->ifindex_user;
++                                      if (minfo->is_at_home) {
++                                              minfo->is_at_home = 0;
++
++                                      }
++                                      newif = minfo->ifindex_user;
++                              }
++                      }
++                      spin_unlock(&minfo->lock);                              
++              }
++              
++              read_unlock_bh(&mn_info_lock);
++      }
++      ma_ctl_upd_iface(dev->ifindex, MA_IFACE_NOT_PRESENT, &newif);
++      mipv6_mdet_del_if(dev->ifindex);
++
++      return NOTIFY_DONE;
++}
++
++struct notifier_block mipv6_mn_dev_notifier = {
++      mn_dev_event,
++      NULL,
++      0 /* check if using zero is ok */
++};
++
++static void deprecate_addr(struct mn_info *minfo)
++{
++      /*
++       * Lookup address from IPv6 address list and set deprecated flag
++       */
++      
++}
++
++/*
++ * Required because we can only modify addresses after the packet is
++ * constructed.  We otherwise mess with higher level protocol
++ * pseudoheaders. With strict protocol layering life would be SO much
++ * easier!  
++ */
++static unsigned int modify_xmit_addrs(unsigned int hooknum,
++                                    struct sk_buff **pskb,
++                                    const struct net_device *in,
++                                    const struct net_device *out,
++                                    int (*okfn) (struct sk_buff *))
++{
++      struct sk_buff *skb = *pskb;
++
++      DEBUG_FUNC();
++      
++      if (skb) {
++              struct ipv6hdr *hdr = skb->nh.ipv6h;
++              struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
++              struct mipv6_bul_entry *bule;
++              struct in6_addr *daddr;
++
++              if (!ipv6_addr_any(&opt->hoa))
++                      daddr = &opt->hoa;
++              else
++                      daddr = &hdr->daddr;
++
++              /* We don't consult bul when sending a BU to avoid deadlock, since
++               * BUL is already locked. 
++               */
++              
++
++              if (opt->mipv6_flags & MIPV6_SND_HAO && 
++                  !(opt->mipv6_flags & MIPV6_SND_BU)) {
++                      write_lock(&bul_lock);
++                      bule = mipv6_bul_get(daddr, &hdr->saddr);
++                      if (!bule) {
++                              write_unlock(&bul_lock);
++                              return NF_ACCEPT;
++                      }
++                      if (!bule->rr || bule->rr->rr_state == RR_DONE || 
++                          bule->flags & MIPV6_BU_F_HOME) {
++                              DEBUG(DBG_DATADUMP, 
++                                    "Replace source address with CoA and reroute");
++                              ipv6_addr_copy(&hdr->saddr, &bule->coa);
++                              skb->nfcache |= NFC_ALTERED;
++                      }
++                      write_unlock(&bul_lock);
++              } else if (opt->mipv6_flags & MIPV6_SND_HAO) {
++                      mipv6_get_care_of_address(&hdr->saddr, &hdr->saddr);
++                      skb->nfcache |= NFC_ALTERED;
++              }
++      }
++      return NF_ACCEPT;
++}
++
++/* We set a netfilter hook so that we can modify outgoing packet's
++ * source addresses 
++ */
++struct nf_hook_ops addr_modify_hook_ops = {
++      {NULL, NULL},           /* List head, no predecessor, no successor */
++      modify_xmit_addrs,
++      PF_INET6,
++      NF_IP6_LOCAL_OUT,
++      NF_IP6_PRI_FIRST        /* Should be of EXTREMELY high priority since we
++                               * do not want to mess with IPSec (possibly
++                               * implemented as packet filter)
++                               */
++};
++
++#define MN_INFO_LEN 77
++
++static int mn_proc_info(char *buffer, char **start, off_t offset,
++                      int length)
++{
++      struct list_head *p;
++      struct mn_info *minfo;
++      int len = 0, skip = 0;
++
++      DEBUG_FUNC();
++
++      read_lock_bh(&mn_info_lock);
++      list_for_each(p, &mn_info_list) {
++              if (len < offset / MN_INFO_LEN) {
++                      skip++;
++                      continue;
++              }
++              if (len >= length)
++                      break;
++              minfo = list_entry(p, struct mn_info, list);
++              spin_lock(&minfo->lock);
++              len += sprintf(buffer + len, "%02d %08x%08x%08x%08x %02x "
++                             "%08x%08x%08x%08x %d %d\n",
++                             minfo->ifindex,
++                             ntohl(minfo->home_addr.s6_addr32[0]),
++                             ntohl(minfo->home_addr.s6_addr32[1]),
++                             ntohl(minfo->home_addr.s6_addr32[2]),
++                             ntohl(minfo->home_addr.s6_addr32[3]),
++                             minfo->home_plen,
++                             ntohl(minfo->ha.s6_addr32[0]),
++                             ntohl(minfo->ha.s6_addr32[1]),
++                             ntohl(minfo->ha.s6_addr32[2]),
++                             ntohl(minfo->ha.s6_addr32[3]),
++                             minfo->is_at_home, minfo->has_home_reg);
++              spin_unlock(&minfo->lock);
++      }
++      read_unlock_bh(&mn_info_lock);
++
++      *start = buffer;
++      if (offset)
++              *start += offset % MN_INFO_LEN;
++
++      len -= offset % MN_INFO_LEN;
++
++      if (len > length)
++              len = length;
++      if (len < 0)
++              len = 0;
++      
++      return len;
++}
++
++int mipv6_mn_ha_nd_update(struct net_device *dev,
++                        struct in6_addr *ha, u8 *lladdr)
++{
++      int valid = 0;
++      struct neighbour *neigh;
++      if ((neigh = ndisc_get_neigh(dev, ha))) {
++              read_lock(&neigh->lock);
++              valid = neigh->nud_state & NUD_VALID;
++              read_unlock(&neigh->lock);
++              if (!valid && lladdr)
++                      neigh_update(neigh, lladdr, NUD_REACHABLE, 0, 1);
++              neigh_release(neigh);
++      }
++      return valid;
++}
++
++int mipv6_mn_ha_probe(struct inet6_ifaddr *ifp, u8 *lladdr)
++{
++      struct mn_info *minfo;
++
++      if (!(minfo = mipv6_mninfo_get_by_home(&ifp->addr)) ||
++          ipv6_addr_any(&minfo->ha))
++              return 0;
++
++      if (mipv6_mn_ha_nd_update(ifp->idev->dev, &minfo->ha, lladdr))
++              mipv6_mdet_retrigger_ho();
++      return 1;
++}
++
++int __init mipv6_mn_init(void)
++{
++      struct net_device *dev;
++
++      DEBUG_FUNC();
++
++      if (mipv6_add_tnl_to_ha())
++              return -ENODEV;
++
++      mipv6_bul_init(MIPV6_BUL_SIZE);
++      mip6_fn.mn_use_hao = mn_use_hao;
++      mip6_fn.mn_check_tunneled_packet = mn_check_tunneled_packet;
++      INIT_TQUEUE(&mv_home_addr_task, mv_home_addr, NULL);
++
++      ma_ctl_init();
++      for (dev = dev_base; dev; dev = dev->next) {
++              if (dev->flags & IFF_UP && 
++                  dev->type != ARPHRD_LOOPBACK && !dev_is_mip6_tnl(dev)) {
++                      ma_ctl_add_iface(dev->ifindex);
++              }
++      } 
++      DEBUG(DBG_INFO, "Multiaccess support initialized");
++
++      register_netdevice_notifier(&mipv6_mn_dev_notifier);
++      register_inet6addr_notifier(&mipv6_mn_inet6addr_notifier);
++
++      ip6ip6_tnl_register_hook(&mipv6_mn_tnl_rcv_send_bu_ops);
++      ip6ip6_tnl_register_hook(&mipv6_mn_tnl_xmit_stats_ops);
++      ip6ip6_tnl_register_hook(&mipv6_mn_tnl_rcv_stats_ops);
++
++      MIPV6_SETCALL(mipv6_set_home, mipv6_mn_set_home);
++
++      mipv6_initialize_mdetect();
++
++      /* COA to home transformation hook */
++      MIPV6_SETCALL(mipv6_get_home_address, mipv6_get_saddr_hook);
++      MIPV6_SETCALL(mipv6_mn_ha_probe, mipv6_mn_ha_probe);
++      MIPV6_SETCALL(mipv6_is_home_addr, mipv6_mn_is_home_addr);
++      proc_net_create("mip6_mninfo", 0, mn_proc_info);
++      /* Set packet modification hook (source addresses) */
++      nf_register_hook(&addr_modify_hook_ops);
++
++      return 0;
++}
++
++void __exit mipv6_mn_exit(void)
++{
++      struct list_head *lh, *tmp;
++      struct mn_info *minfo;
++      DEBUG_FUNC();
++      
++      mip6_fn.mn_use_hao = NULL;
++      mip6_fn.mn_check_tunneled_packet = NULL;
++      
++      MIPV6_RESETCALL(mipv6_set_home);
++      MIPV6_RESETCALL(mipv6_get_home_address);
++      MIPV6_RESETCALL(mipv6_mn_ha_probe);
++      MIPV6_RESETCALL(mipv6_is_home_addr);
++      nf_unregister_hook(&addr_modify_hook_ops);
++      proc_net_remove("mip6_mninfo");
++      mipv6_shutdown_mdetect();
++      ip6ip6_tnl_unregister_hook(&mipv6_mn_tnl_rcv_stats_ops);
++      ip6ip6_tnl_unregister_hook(&mipv6_mn_tnl_xmit_stats_ops);
++      ip6ip6_tnl_unregister_hook(&mipv6_mn_tnl_rcv_send_bu_ops);
++      ma_ctl_clean();
++
++      unregister_inet6addr_notifier(&mipv6_mn_inet6addr_notifier);
++      unregister_netdevice_notifier(&mipv6_mn_dev_notifier);
++      write_lock_bh(&mn_info_lock);
++
++      list_for_each_safe(lh, tmp, &mn_info_list) {
++              minfo = list_entry(lh, struct mn_info, list);
++              if (minfo->is_at_home == MN_NOT_AT_HOME) 
++                      deprecate_addr(minfo);
++              list_del(&minfo->list);
++              kfree(minfo);
++      }
++      write_unlock_bh(&mn_info_lock);
++      mipv6_bul_exit();
++      flush_scheduled_tasks();
++      mipv6_del_tnl_to_ha();
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mn.h linux-2.4.25/net/ipv6/mobile_ip6/mn.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/mn.h  1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/mn.h      2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,96 @@
++/*
++ *      MIPL Mobile IPv6 Mobile Node header file
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _MN_H
++#define _MN_H
++
++#include <linux/in6.h>
++
++/* constants for sending of BUs*/
++#define HA_BU_DEF_LIFETIME 10000
++#define CN_BU_DEF_LIFETIME 420 /* Max lifetime for RR bindings from RFC 3775 */
++#define DUMB_CN_BU_LIFETIME 600 /* BUL entry lifetime in case of dumb CN */
++#define ROUTER_BU_DEF_LIFETIME 30 /* For packet forwarding from previous coa */
++#define ERROR_DEF_LIFETIME DUMB_CN_BU_LIFETIME
++
++extern rwlock_t mn_info_lock;
++
++#define MN_NOT_AT_HOME 0
++#define MN_RETURNING_HOME 1
++#define MN_AT_HOME 2
++
++/*
++ * Mobile Node information record
++ */
++struct mn_info {
++      struct in6_addr home_addr;
++      struct in6_addr ha;
++      __u8 home_plen;
++      __u8 is_at_home;
++      __u8 has_home_reg;
++      __u8 man_conf;
++      int ifindex;
++      int ifindex_user; 
++      unsigned long home_addr_expires;
++      unsigned short dhaad_id;
++      struct list_head list;
++      spinlock_t lock;
++};
++
++/* prototypes for interface functions */
++int mipv6_mn_init(void);
++void mipv6_mn_exit(void);
++
++struct handoff;
++
++/* Interface to movement detection */
++int mipv6_mobile_node_moved(struct handoff *ho);
++
++void mipv6_mn_send_home_na(struct in6_addr *haddr);
++/* Init home reg. with coa */
++int init_home_registration(struct in6_addr *home_addr, struct in6_addr *coa);
++
++/* mn_info functions that require locking by caller */
++struct mn_info *mipv6_mninfo_get_by_home(struct in6_addr *haddr);
++
++struct mn_info *mipv6_mninfo_get_by_ha(struct in6_addr *home_agent);
++
++struct mn_info *mipv6_mninfo_get_by_id(unsigned short id);
++
++/* "safe" mn_info functions */
++void mipv6_mninfo_add(int ifindex, struct in6_addr *home_addr, int plen, 
++                    int isathome, unsigned long lifetime, struct in6_addr *ha, 
++                    int ha_plen, unsigned long ha_lifetime, int man_conf);
++
++int mipv6_mninfo_del(struct in6_addr *home_addr, int del_dyn_only);
++
++void mipv6_mn_set_home_reg(struct in6_addr *home_addr, int has_home_reg);
++
++int mipv6_mn_is_at_home(struct in6_addr *addr);
++
++int mipv6_mn_is_home_addr(struct in6_addr *addr);
++
++__u32 mipv6_mn_get_bulifetime(struct in6_addr *home_addr, 
++                            struct in6_addr *coa, __u8 flags);
++int mn_cn_handoff(void *rawentry, void *args, unsigned long *sortkey);
++
++int mipv6_mn_ha_nd_update(struct net_device *dev,
++                        struct in6_addr *ha, u8 *lladdr);
++
++struct bul_inval_args {
++      int all_rr_states;
++      struct in6_addr *cn;
++      struct in6_addr *mn;
++};
++
++int mn_bul_invalidate(void *rawentry, void *args, unsigned long *sortkey);
++
++#endif /* _MN_H */
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mobhdr.h linux-2.4.25/net/ipv6/mobile_ip6/mobhdr.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/mobhdr.h      1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/mobhdr.h  2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,101 @@
++/*
++ *      MIPL Mobile IPv6 Mobility Header send and receive
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _MOBHDR_H
++#define _MOBHDR_H
++
++#include <net/mipv6.h>
++
++/* RR states for mipv6_send_bu() */
++#define RR_INIT                       0x00
++#define RR_WAITH              0x01
++#define RR_WAITC              0x02
++#define RR_WAITHC             0x13
++#define RR_DONE                       0x10
++
++#define MH_UNKNOWN_CN 1
++#define MH_AUTH_FAILED 2
++#define MH_SEQUENCE_MISMATCH 3
++
++struct mipv6_bul_entry;
++struct sk_buff;
++
++int mipv6_mh_common_init(void);
++void mipv6_mh_common_exit(void);
++int mipv6_mh_mn_init(void);
++void mipv6_mh_mn_exit(void);
++
++struct mipv6_mh_opt {
++      struct mipv6_mo_alt_coa         *alt_coa;
++      struct mipv6_mo_nonce_indices   *nonce_indices;
++      struct mipv6_mo_bauth_data      *auth_data;
++      struct mipv6_mo_br_advice       *br_advice;
++      int freelen;
++      int totlen;
++      u8 *next_free;
++      u8 data[0];
++};
++
++struct mobopt {
++      struct mipv6_mo_alt_coa         *alt_coa;
++      struct mipv6_mo_nonce_indices   *nonce_indices;
++      struct mipv6_mo_bauth_data      *auth_data;
++      struct mipv6_mo_br_advice       *br_advice;
++};
++
++struct mipv6_mh_opt *alloc_mh_opts(int totlen);
++int append_mh_opt(struct mipv6_mh_opt *ops, u8 type, u8 len, void *data);
++int parse_mo_tlv(void *mos, int len, struct mobopt *opts);
++int mipv6_add_pad(u8 *data, int n);
++
++struct mipv6_auth_parm {
++      struct in6_addr *coa;
++      struct in6_addr *cn_addr;
++      __u8 *k_bu;
++};
++
++int send_mh(struct in6_addr *daddr, struct in6_addr *saddr, 
++          u8 msg_type, u8 msg_len, u8 *msg,
++          struct in6_addr *hao_addr, struct in6_addr *rth_addr,
++          struct mipv6_mh_opt *ops, struct mipv6_auth_parm *parm);
++
++int mipv6_mh_register(int type, int (*func)(struct sk_buff *,
++      struct in6_addr *, struct in6_addr *, 
++      struct in6_addr *, struct in6_addr *, struct mipv6_mh *));
++
++void mipv6_mh_unregister(int type);
++
++int mipv6_send_brr(struct in6_addr *saddr, struct in6_addr *daddr,
++                 struct mipv6_mh_opt *ops);
++
++int mipv6_send_bu(struct in6_addr *saddr, struct in6_addr *daddr, 
++                struct in6_addr *coa, __u32 initdelay, 
++                __u32 maxackdelay, __u8 exp, __u8 flags,
++                __u32 lifetime, struct mipv6_mh_opt *ops);
++
++int mipv6_send_be(struct in6_addr *saddr, struct in6_addr *daddr, 
++                struct in6_addr *home, __u8 status);
++
++int mipv6_send_ba(struct in6_addr *saddr, struct in6_addr *daddr, 
++                struct in6_addr *auth_coa, struct in6_addr *rep_coa,
++                u8 status, u16 sequence, u32 lifetime, u8 *k_bu);
++
++/* Binding Authentication Data Option routines */
++#define MAX_HASH_LENGTH 20
++#define MIPV6_RR_MAC_LENGTH 12
++
++int mipv6_auth_build(struct in6_addr *cn_addr, struct in6_addr *coa, 
++                   __u8 *opt, __u8 *aud_data, __u8 *k_bu);
++
++int mipv6_auth_check(struct in6_addr *cn_addr, struct in6_addr *coa, 
++                   __u8 *opt, __u8 optlen, struct mipv6_mo_bauth_data *aud, 
++                   __u8 *k_bu);
++#endif /* _MOBHDR_H */
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mobhdr_common.c linux-2.4.25/net/ipv6/mobile_ip6/mobhdr_common.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/mobhdr_common.c       1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/mobhdr_common.c   2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,1210 @@
++/*
++ *    Mobile IPv6 Mobility Header Common Functions
++ *
++ *    Authors:
++ *    Antti Tuominen <ajtuomin@tml.hut.fi>
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ *
++ */
++
++#include <linux/autoconf.h>
++#include <linux/types.h>
++#include <linux/in6.h>
++#include <linux/skbuff.h>
++#include <linux/ipsec.h>
++#include <linux/init.h>
++#include <net/ipv6.h>
++#include <net/ip6_route.h>
++#include <net/addrconf.h>
++#include <net/mipv6.h>
++#include <net/checksum.h>
++#include <net/protocol.h>
++
++#include "stats.h"
++#include "debug.h"
++#include "mobhdr.h"
++#include "bcache.h"
++
++#include "rr_crypto.h"
++#include "exthdrs.h"
++#include "config.h"
++
++#define MIPV6_MH_MAX MIPV6_MH_BE
++struct mh_proto {
++      int     (*func) (struct sk_buff *,
++                       struct in6_addr *, struct in6_addr *, 
++                       struct in6_addr *, struct in6_addr *, 
++                       struct mipv6_mh *);
++};
++
++static struct mh_proto mh_rcv[MIPV6_MH_MAX];
++
++int mipv6_mh_register(int type, int (*func)(struct sk_buff *,
++      struct in6_addr *, struct in6_addr *, 
++      struct in6_addr *, struct in6_addr *, struct mipv6_mh *))
++{
++      if (mh_rcv[type].func != NULL)
++              return -1;
++
++      mh_rcv[type].func = func;
++
++      return 0;
++}
++
++void mipv6_mh_unregister(int type)
++{
++      if (type < 0 || type > MIPV6_MH_MAX)
++              return;
++
++      mh_rcv[type].func = NULL;
++}
++
++struct socket *mipv6_mh_socket = NULL;
++
++/* TODO: Fix fragmentation */
++static int dstopts_getfrag(
++      const void *data, struct in6_addr *addr,
++      char *buff, unsigned int offset, unsigned int len)
++{
++      memcpy(buff, data + offset, len);
++      return 0;
++}
++
++struct mipv6_mh_opt *alloc_mh_opts(int totlen)
++{
++      struct mipv6_mh_opt *ops;
++
++      ops = kmalloc(sizeof(*ops) + totlen, GFP_ATOMIC);
++      if (ops == NULL)
++              return NULL;
++
++      memset(ops, 0, sizeof(*ops));
++      ops->next_free = ops->data;
++      ops->freelen = totlen;
++
++      return ops;
++}
++
++int append_mh_opt(struct mipv6_mh_opt *ops, u8 type, u8 len, void *data)
++{
++      struct mipv6_mo *mo;
++
++      if (ops->next_free == NULL) {
++              DEBUG(DBG_ERROR, "No free room for option");
++              return -ENOMEM;
++      }
++      if (ops->freelen < len + 2) {
++              DEBUG(DBG_ERROR, "No free room for option");
++              return -ENOMEM;
++      }
++      else {
++              ops->freelen -= (len + 2);
++              ops->totlen += (len + 2);
++      }
++
++      mo = (struct mipv6_mo *)ops->next_free;
++      mo->type = type;
++      mo->length = len;
++
++      switch (type) {
++      case MIPV6_OPT_ALTERNATE_COA:
++              ops->alt_coa = (struct mipv6_mo_alt_coa *)mo;
++              ipv6_addr_copy(&ops->alt_coa->addr, (struct in6_addr *)data);
++              break;
++      case MIPV6_OPT_NONCE_INDICES:
++              DEBUG(DBG_INFO, "Added nonce indices pointer");
++              ops->nonce_indices = (struct mipv6_mo_nonce_indices *)mo;
++              ops->nonce_indices->home_nonce_i = *(__u16 *)data;
++              ops->nonce_indices->careof_nonce_i = *((__u16 *)data + 1);
++              break;
++      case MIPV6_OPT_AUTH_DATA:
++              DEBUG(DBG_INFO, "Added opt auth_data pointer");
++              ops->auth_data = (struct mipv6_mo_bauth_data *)mo;
++              break;
++      case MIPV6_OPT_BIND_REFRESH_ADVICE:
++              ops->br_advice = (struct mipv6_mo_br_advice *)mo;
++              ops->br_advice->refresh_interval = htons(*(u16 *)data);
++              break;
++      default:
++              DEBUG(DBG_ERROR, "Unknow option type");
++              break;
++      }
++
++      if (ops->freelen == 0)
++              ops->next_free = NULL;
++      else
++              ops->next_free += (len + 2);
++
++      return 0;
++}
++
++/*
++ * Calculates required padding with xn + y requirement with offset
++ */
++static inline int optpad(int xn, int y, int offset)
++{
++      return ((y - offset) & (xn - 1));
++}
++
++static int option_pad(int type, int offset)
++{
++      if (type == MIPV6_OPT_ALTERNATE_COA)
++              return optpad(8, 6, offset); /* 8n + 6 */
++      if (type == MIPV6_OPT_BIND_REFRESH_ADVICE ||
++          type == MIPV6_OPT_NONCE_INDICES)
++              return optpad(2, 0, offset); /* 2n */
++      return 0;
++}
++
++/*
++ * Add Pad1 or PadN option to data
++ */
++int mipv6_add_pad(u8 *data, int n)
++{
++      struct mipv6_mo_padn *padn;
++
++      if (n <= 0) return 0;
++      if (n == 1) {
++              *data = MIPV6_OPT_PAD1;
++              return 1;
++      }
++      padn = (struct mipv6_mo_padn *)data;
++      padn->type = MIPV6_OPT_PADN;
++      padn->length = n - 2;
++      memset(padn->data, 0, n - 2);
++      return n;
++}
++
++/*
++ * Write options to mobility header buffer
++ */
++static int prepare_mh_opts(u8 *optdata, int off, struct mipv6_mh_opt *ops)
++{
++      u8 *nextopt = optdata;
++      int offset = off, pad = 0;
++
++      if (ops == NULL) {
++              nextopt = NULL;
++              return -1;
++      }
++
++      if (ops->alt_coa) {
++              pad = option_pad(MIPV6_OPT_ALTERNATE_COA, offset);
++              nextopt += mipv6_add_pad(nextopt, pad);
++              memcpy(nextopt, ops->alt_coa, sizeof(struct mipv6_mo_alt_coa));
++              nextopt += sizeof(struct mipv6_mo_alt_coa);
++              offset += pad + sizeof(struct mipv6_mo_alt_coa);
++      }
++
++      if (ops->br_advice) {
++              pad = option_pad(MIPV6_OPT_BIND_REFRESH_ADVICE, offset);
++              nextopt += mipv6_add_pad(nextopt, pad);
++              memcpy(nextopt, ops->br_advice, sizeof(struct mipv6_mo_br_advice));
++              nextopt += sizeof(struct mipv6_mo_br_advice);
++              offset += pad + sizeof(struct mipv6_mo_br_advice);
++      }
++
++      if (ops->nonce_indices) {
++              pad = option_pad(MIPV6_OPT_NONCE_INDICES, offset);
++              nextopt += mipv6_add_pad(nextopt, pad);
++              memcpy(nextopt, ops->nonce_indices, sizeof(struct mipv6_mo_nonce_indices));
++              nextopt += sizeof(struct mipv6_mo_nonce_indices);
++              offset += pad + sizeof(struct mipv6_mo_nonce_indices);
++      }
++
++      if (ops->auth_data) {
++              /* This option should always be the last.  Header
++               * length must be a multiple of 8 octects, so we pad
++               * if necessary. */
++              pad = optpad(8, 0, offset + ops->auth_data->length + 2);
++              nextopt += mipv6_add_pad(nextopt, pad);
++              memcpy(nextopt, ops->auth_data, ops->auth_data->length + 2);
++              nextopt += ops->auth_data->length + 2;
++      }
++      nextopt = NULL;
++
++      return 0;
++}
++
++static int calculate_mh_opts(struct mipv6_mh_opt *ops, int mh_len)
++{
++      int offset = mh_len;
++
++      if (ops == NULL)
++              return 0;
++
++      if (ops->alt_coa)
++              offset += sizeof(struct mipv6_mo_alt_coa)
++                      + option_pad(MIPV6_OPT_ALTERNATE_COA, offset);
++
++      if (ops->br_advice)
++              offset += sizeof(struct mipv6_mo_br_advice)
++                      + option_pad(MIPV6_OPT_BIND_REFRESH_ADVICE, offset);
++
++      if (ops->nonce_indices)
++              offset += sizeof(struct mipv6_mo_nonce_indices)
++                      + option_pad(MIPV6_OPT_NONCE_INDICES, offset);
++
++      if (ops->auth_data) /* no alignment */
++              offset += ops->auth_data->length + 2;
++
++      return offset - mh_len;
++}
++
++/*
++ *
++ * Mobility Header Message send functions
++ *
++ */
++
++/**
++ * send_mh - builds and sends a MH msg
++ *
++ * @daddr: destination address for packet
++ * @saddr: source address for packet
++ * @msg_type: type of MH
++ * @msg_len: message length
++ * @msg: MH type specific data
++ * @hao_addr: home address for home address option
++ * @rth_addr: routing header address
++ * @ops: mobility options
++ * @parm: auth data
++ *
++ * Builds MH, appends the type specific msg data to the header and
++ * sends the packet with a home address option, if a home address was
++ * given. Returns 0, if everything succeeded and a negative error code
++ * otherwise.
++ **/
++int send_mh(struct in6_addr *daddr, 
++          struct in6_addr *saddr, 
++          u8 msg_type, u8 msg_len, u8 *msg,
++          struct in6_addr *hao_addr,
++          struct in6_addr *rth_addr,
++          struct mipv6_mh_opt *ops,
++          struct mipv6_auth_parm *parm)
++{
++      struct flowi fl;
++      struct mipv6_mh *mh; 
++      struct sock *sk = mipv6_mh_socket->sk;
++      struct ipv6_txoptions *txopt = NULL;
++      int tot_len = sizeof(struct mipv6_mh) + msg_len;
++      int padded_len = 0, txopt_len = 0;
++
++      DEBUG_FUNC();
++      /* Add length of options */
++      tot_len += calculate_mh_opts(ops, tot_len);
++      /* Needs to be a multiple of 8 octets */
++      padded_len = tot_len + optpad(8, 0, tot_len);
++
++      mh = sock_kmalloc(sk, padded_len, GFP_ATOMIC);
++      if (!mh) {
++              DEBUG(DBG_ERROR, "memory allocation failed");
++              return -ENOMEM;
++      }
++
++      memset(&fl, 0, sizeof(fl)); 
++      fl.proto = IPPROTO_MOBILITY;
++      fl.fl6_dst = daddr;
++      fl.fl6_src = saddr;
++      fl.fl6_flowlabel = 0;
++      fl.oif = sk->bound_dev_if;
++
++      if (hao_addr || rth_addr) {
++              __u8 *opt_ptr;
++
++              if (hao_addr)
++                      txopt_len += sizeof(struct mipv6_dstopt_homeaddr) + 6;
++              if (rth_addr)
++                      txopt_len += sizeof(struct rt2_hdr);
++
++              txopt_len += sizeof(*txopt);
++              txopt = sock_kmalloc(sk, txopt_len, GFP_ATOMIC);
++              if (txopt == NULL) {
++                      DEBUG(DBG_ERROR, "No socket space left");
++                      sock_kfree_s(sk, mh, padded_len);
++                      return -ENOMEM;
++              }
++              memset(txopt, 0, txopt_len);
++              txopt->tot_len = txopt_len;
++              opt_ptr = (__u8 *) (txopt + 1);
++              if (hao_addr) {
++                      int holen = sizeof(struct mipv6_dstopt_homeaddr) + 6;
++                      txopt->dst1opt = (struct ipv6_opt_hdr *) opt_ptr;
++                      txopt->opt_flen += holen;
++                      opt_ptr += holen;
++                      mipv6_append_dst1opts(txopt->dst1opt, saddr, 
++                                            NULL, holen);
++                      txopt->mipv6_flags = MIPV6_SND_HAO | MIPV6_SND_BU;
++              }
++              if (rth_addr) {
++                      int rtlen = sizeof(struct rt2_hdr);
++                      txopt->srcrt2 = (struct ipv6_rt_hdr *) opt_ptr;
++                      txopt->opt_nflen += rtlen;
++                      opt_ptr += rtlen;
++                      mipv6_append_rt2hdr(txopt->srcrt2, rth_addr);
++              }
++      }
++
++      /* Fill in the fields of MH */
++      mh->payload = NEXTHDR_NONE;
++      mh->length = (padded_len >> 3) - 1;     /* Units of 8 octets - 1 */
++      mh->type = msg_type;
++      mh->reserved = 0;
++      mh->checksum = 0;
++
++      memcpy(mh->data, msg, msg_len);
++      prepare_mh_opts(mh->data + msg_len, msg_len + sizeof(*mh), ops);
++      /* If BAD is present, this is already done. */
++      mipv6_add_pad((u8 *)mh + tot_len, padded_len - tot_len);
++      
++      if (parm && parm->k_bu && ops && ops->auth_data) {
++              /* Calculate the position of the authorization data before adding checksum*/
++              mipv6_auth_build(parm->cn_addr, parm->coa, (__u8 *)mh, 
++                               (__u8 *)mh + padded_len - MIPV6_RR_MAC_LENGTH, parm->k_bu);
++      }
++      /* Calculate the MH checksum */
++      mh->checksum = csum_ipv6_magic(fl.fl6_src, fl.fl6_dst, 
++                                     padded_len, IPPROTO_MOBILITY,
++                                     csum_partial((char *)mh, padded_len, 0));
++      ip6_build_xmit(sk, dstopts_getfrag, mh, &fl, padded_len, txopt, 255,
++                     MSG_DONTWAIT);
++      /* dst cache must be cleared so RR messages can be routed through 
++         different interfaces */
++      sk_dst_reset(sk);
++
++      if (txopt_len)
++              sock_kfree_s(sk, txopt, txopt_len);
++      sock_kfree_s(sk, mh, padded_len);
++      return 0;
++}
++
++/**
++ * mipv6_send_brr - send a Binding Refresh Request 
++ * @saddr: source address for BRR
++ * @daddr: destination address for BRR
++ * @ops: mobility options
++ *
++ * Sends a binding request.  On a mobile node, use the mobile node's
++ * home address for @saddr.  Returns 0 on success, negative on
++ * failure.
++ **/
++int mipv6_send_brr(struct in6_addr *saddr, struct in6_addr *daddr,
++                 struct mipv6_mh_opt *ops)
++{
++      struct mipv6_mh_brr br;
++
++      memset(&br, 0, sizeof(br));
++      /* We don't need to explicitly add a RH to brr, since it will be 
++       * included automatically, if a BCE exists 
++       */
++      MIPV6_INC_STATS(n_brr_sent);
++      return send_mh(daddr, saddr, MIPV6_MH_BRR, sizeof(br), (u8 *)&br,
++                     NULL, NULL, ops, NULL);
++}
++
++/**
++ * mipv6_send_ba - send a Binding Acknowledgement 
++ * @saddr: source address for BA
++ * @daddr: destination address for BA 
++ * @reply_coa: destination care-of address of MN
++ * @auth_coa: care-of address of MN used for authentication
++ * @status: status field value
++ * @sequence: sequence number from BU
++ * @lifetime: granted lifetime for binding in seconds
++ * @ops: mobility options
++ *
++ * Send a binding acknowledgement.  On a mobile node, use the mobile
++ * node's home address for saddr.  Returns 0 on success, non-zero on
++ * failure.
++ **/
++int mipv6_send_ba(struct in6_addr *saddr, struct in6_addr *daddr, 
++                struct in6_addr *auth_coa, struct in6_addr *rep_coa,
++                u8 status, u16 sequence, u32 lifetime, u8 *k_bu)
++{
++      struct mipv6_mh_ba ba;
++      struct mipv6_auth_parm parm;
++      struct mipv6_mh_opt *ops = NULL; 
++      int ops_len = 0, ret = 0;
++      struct mipv6_bce bc_entry;
++      int coming_home = 0;
++      int bypass_tnl = 0;
++
++      memset(&ba, 0, sizeof(ba));
++      
++      ba.status = status;
++      ba.sequence = htons(sequence);
++      ba.lifetime = htons(lifetime >> 2);
++      
++      DEBUG(DBG_INFO, "sending a status %d BA %s authenticator to MN \n"
++            "%x:%x:%x:%x:%x:%x:%x:%x  at care of address \n" 
++            "%x:%x:%x:%x:%x:%x:%x:%x : with lifetime %d and \n" 
++            " sequence number %d",
++            status, k_bu ? "with" : "without", 
++            NIPV6ADDR(daddr), NIPV6ADDR(auth_coa), lifetime, sequence);
++
++      memset(&parm, 0, sizeof(parm));
++      parm.coa = auth_coa;
++      parm.cn_addr = saddr;
++
++      if (k_bu) {
++              ops_len += sizeof(struct mipv6_mo_bauth_data) + 
++                      MIPV6_RR_MAC_LENGTH;
++              parm.k_bu = k_bu;
++      }
++
++      if (mip6node_cnf.binding_refresh_advice) {
++              ops_len += sizeof(struct mipv6_mo_br_advice);
++      }
++      if (ops_len) {
++              ops = alloc_mh_opts(ops_len);
++              if (ops == NULL) {
++                      DEBUG(DBG_WARNING, "Out of memory");
++                      return -ENOMEM;
++              }
++              if (mip6node_cnf.binding_refresh_advice > 0) {
++                      if (append_mh_opt(ops, MIPV6_OPT_BIND_REFRESH_ADVICE, 2,
++                                        &mip6node_cnf.binding_refresh_advice) < 0) {
++                              DEBUG(DBG_WARNING, "Adding BRA failed");
++                              if (ops)
++                                      kfree(ops);
++                              return -ENOMEM;
++                      }
++              }
++              if (k_bu) {
++                      if (append_mh_opt(ops, MIPV6_OPT_AUTH_DATA,
++                                        MIPV6_RR_MAC_LENGTH, NULL) < 0) {
++                              DEBUG(DBG_WARNING, "Adding BAD failed");
++                              if (ops)
++                                      kfree(ops);
++                              return -ENOMEM;
++                      }
++              }
++      }
++      coming_home = !ipv6_addr_cmp(rep_coa, daddr);
++
++      bypass_tnl = (coming_home &&
++                    !mipv6_bcache_get(daddr, saddr, &bc_entry) &&
++                    bc_entry.flags&MIPV6_BU_F_HOME && 
++                    status >= 128);
++
++      if (bypass_tnl && mip6_fn.bce_tnl_rt_del)
++              mip6_fn.bce_tnl_rt_del(&bc_entry.coa,
++                                     &bc_entry.our_addr,
++                                     &bc_entry.home_addr);
++
++      if (coming_home)
++              ret = send_mh(daddr, saddr, MIPV6_MH_BA, sizeof(ba), (u8 *)&ba,
++                            NULL, NULL, ops, &parm);
++      else
++              ret = send_mh(daddr, saddr, MIPV6_MH_BA, sizeof(ba), (u8 *)&ba,
++                            NULL, rep_coa, ops, &parm);
++
++      if (bypass_tnl && mip6_fn.bce_tnl_rt_add)
++              mip6_fn.bce_tnl_rt_add(&bc_entry.coa,
++                                     &bc_entry.our_addr,
++                                     &bc_entry.home_addr);
++      
++      if (ret == 0) {
++              if (status < 128) {
++                      MIPV6_INC_STATS(n_ba_sent);
++              } else {
++                      MIPV6_INC_STATS(n_ban_sent);
++              }
++      }
++
++      if (ops)
++              kfree(ops);
++
++      return 0;
++}
++
++/**
++ * mipv6_send_be - send a Binding Error message
++ * @saddr: source address for BE
++ * @daddr: destination address for BE
++ * @home: Home Address in offending packet (if any)
++ *
++ * Sends a binding error.  On a mobile node, use the mobile node's
++ * home address for @saddr.  Returns 0 on success, negative on
++ * failure.
++ **/
++int mipv6_send_be(struct in6_addr *saddr, struct in6_addr *daddr, 
++                struct in6_addr *home, __u8 status)
++{
++      struct mipv6_mh_be be;
++      int ret = 0;
++      struct mipv6_bce bc_entry;
++      int bypass_tnl = 0;
++
++      if (ipv6_addr_is_multicast(daddr))
++              return -EINVAL;
++
++      memset(&be, 0, sizeof(be));
++      be.status = status;
++      if (home)
++              ipv6_addr_copy(&be.home_addr, home);
++
++      if (mipv6_bcache_get(daddr, saddr, &bc_entry) == 0 &&
++          bc_entry.flags&MIPV6_BU_F_HOME)
++              bypass_tnl = 1;
++
++      if (bypass_tnl && mip6_fn.bce_tnl_rt_del)
++              mip6_fn.bce_tnl_rt_del(&bc_entry.coa,
++                                     &bc_entry.our_addr,
++                                     &bc_entry.home_addr);
++
++      ret = send_mh(daddr, saddr, MIPV6_MH_BE, sizeof(be), (u8 *)&be,
++                    NULL, NULL, NULL, NULL);
++      
++      if (bypass_tnl && mip6_fn.bce_tnl_rt_add)
++              mip6_fn.bce_tnl_rt_add(&bc_entry.coa,
++                                     &bc_entry.our_addr,
++                                     &bc_entry.home_addr);
++
++      if (ret == 0)
++              MIPV6_INC_STATS(n_be_sent);
++
++      return ret;
++}
++
++/**
++ * mipv6_send_addr_test - send a HoT or CoT message
++ * @saddr: source address
++ * @daddr: destination address
++ * @msg_type: HoT or CoT message
++ * @init: HoTI or CoTI message
++ *
++ * Send a reply to HoTI or CoTI message. 
++ **/
++static int mipv6_send_addr_test(struct in6_addr *saddr,
++                              struct in6_addr *daddr,
++                              int msg_type,
++                              struct mipv6_mh_addr_ti *init)
++{
++      u_int8_t                        *kgen_token = NULL;
++      struct mipv6_mh_addr_test       addr_test;      
++      struct mipv6_rr_nonce           *nonce;
++      struct mipv6_mh_opt *ops = NULL;
++      int ret = 0;
++
++      DEBUG_FUNC();
++
++      if ((nonce = mipv6_rr_get_new_nonce())== NULL) {
++              DEBUG(DBG_WARNING, "Nonce creation failed");
++              return 0;
++      } 
++      if (mipv6_rr_cookie_create(daddr, &kgen_token, nonce->index)) {
++              DEBUG(DBG_WARNING, "No cookie");
++              return 0;
++      }
++
++      addr_test.nonce_index = nonce->index;
++      memcpy(addr_test.init_cookie, init->init_cookie,
++                      MIPV6_RR_COOKIE_LENGTH);
++      memcpy(addr_test.kgen_token, kgen_token,
++                      MIPV6_RR_COOKIE_LENGTH);
++
++      /* No options defined */
++      ret = send_mh(daddr, saddr, msg_type, sizeof(addr_test),
++                    (u8 *)&addr_test, NULL, NULL, ops, NULL);
++
++      if (ret == 0) {
++              if (msg_type == MIPV6_MH_HOT) {
++                      MIPV6_INC_STATS(n_hot_sent);
++              } else {
++                      MIPV6_INC_STATS(n_cot_sent);
++              }
++      }
++
++      return 0;
++}
++
++static void bc_cache_add(int ifindex, struct in6_addr *daddr,
++                       struct in6_addr *haddr, struct in6_addr *coa,
++                       struct in6_addr *rep_coa, __u32 lifetime,
++                       __u16 sequence, __u8 flags, __u8 *k_bu)
++{
++      __u8 ba_status = SUCCESS;
++
++      if (lifetime >  MAX_RR_BINDING_LIFE)
++              lifetime = MAX_RR_BINDING_LIFE;
++
++      if (mipv6_bcache_add(ifindex, daddr, haddr, coa, lifetime,
++                           sequence, flags, CACHE_ENTRY) != 0) {
++              DEBUG(DBG_ERROR, "binding failed.");
++              ba_status = INSUFFICIENT_RESOURCES;
++      } 
++
++      if (flags & MIPV6_BU_F_ACK) {
++              DEBUG(DBG_INFO, "sending ack (code=%d)", ba_status);
++              mipv6_send_ba(daddr, haddr, coa, rep_coa, ba_status, sequence,
++                            lifetime, k_bu);
++      }
++}
++
++static void bc_cn_home_add(int ifindex, struct in6_addr *daddr, 
++                         struct in6_addr *haddr, struct in6_addr *coa,
++                         struct in6_addr *rep_coa, __u32 lifetime,
++                         __u16 sequence, __u8 flags, __u8 *k_bu)
++{
++      mipv6_send_ba(daddr, haddr, coa, rep_coa,
++                    HOME_REGISTRATION_NOT_SUPPORTED,
++                    sequence, lifetime, k_bu);
++}
++
++static void bc_cache_delete(struct in6_addr *daddr, struct in6_addr *haddr, 
++                          struct in6_addr *coa, struct in6_addr *rep_coa,
++                          __u16 sequence, __u8 flags,
++                          __u8 *k_bu)
++{
++      __u8 status = SUCCESS;
++
++      /* Cached Care-of Address Deregistration */
++      if (mipv6_bcache_exists(haddr, daddr) == CACHE_ENTRY) {
++              mipv6_bcache_delete(haddr, daddr, CACHE_ENTRY);
++      } else {
++              DEBUG(DBG_INFO, "entry is not in cache");
++              status = REASON_UNSPECIFIED;
++      }
++      if (flags & MIPV6_BU_F_ACK) {
++              mipv6_send_ba(daddr, haddr, coa, rep_coa, status, sequence, 
++                            0, k_bu);
++      }
++}
++
++static void bc_cn_home_delete(struct in6_addr *daddr, struct in6_addr *haddr, 
++                            struct in6_addr *coa, struct in6_addr *rep_coa,
++                            __u16 sequence, __u8 flags,
++                            __u8 *k_bu)
++{
++}
++
++/**
++ * parse_mo_tlv - Parse TLV-encoded Mobility Options
++ * @mos: pointer to Mobility Options
++ * @len: total length of options
++ * @opts: structure to store option pointers
++ *
++ * Parses Mobility Options passed in @mos.  Stores pointers in @opts
++ * to all valid mobility options found in @mos.  Unknown options and
++ * padding (%MIPV6_OPT_PAD1 and %MIPV6_OPT_PADN) is ignored and
++ * skipped.
++ **/
++int parse_mo_tlv(void *mos, int len, struct mobopt *opts)
++{
++      struct mipv6_mo *curr = (struct mipv6_mo *)mos;
++      int left = len;
++
++      while (left > 0) {
++              int optlen = 0;
++              if (curr->type == MIPV6_OPT_PAD1)
++                      optlen = 1;
++              else
++                      optlen = 2 + curr->length;
++
++              if (optlen > left)
++                      goto bad;
++
++              switch (curr->type) {
++              case MIPV6_OPT_PAD1:
++                      DEBUG(DBG_DATADUMP, "MIPV6_OPT_PAD1 at %x", curr);
++                      break;
++              case MIPV6_OPT_PADN:
++                      DEBUG(DBG_DATADUMP, "MIPV6_OPT_PADN at %x", curr);
++                      break;
++              case MIPV6_OPT_ALTERNATE_COA:
++                      DEBUG(DBG_DATADUMP, "MIPV6_OPT_ACOA at %x", curr);
++                      opts->alt_coa = (struct mipv6_mo_alt_coa *)curr;
++                      break;
++              case MIPV6_OPT_NONCE_INDICES:
++                      DEBUG(DBG_DATADUMP, "MIPV6_OPT_NONCE_INDICES at %x", curr);
++                      opts->nonce_indices = 
++                              (struct mipv6_mo_nonce_indices *)curr;
++                      break;
++              case MIPV6_OPT_AUTH_DATA:
++                      DEBUG(DBG_DATADUMP, "MIPV6_OPT_AUTH_DATA at %x", curr);
++                      opts->auth_data = (struct mipv6_mo_bauth_data *)curr;
++                      break;
++              case MIPV6_OPT_BIND_REFRESH_ADVICE:
++                      DEBUG(DBG_DATADUMP, "MIPV6_OPT_BIND_REFRESH_ADVICE at %x", curr);
++                      opts->br_advice = (struct mipv6_mo_br_advice *)curr;
++                      break;
++              default:
++                      DEBUG(DBG_INFO, "MO Unknown option type %d at %x, ignoring.",
++                             curr->type, curr);
++                      /* unknown mobility option, ignore and skip */
++              }
++
++              (u8 *)curr += optlen;
++              left -= optlen;
++      }
++
++      if (left == 0)
++              return 0;
++ bad:
++      return -1;
++}
++
++/*
++ *
++ * Mobility Header Message handlers
++ *
++ */
++
++static int mipv6_handle_mh_testinit(struct sk_buff *skb,
++                                  struct in6_addr *cn,
++                                  struct in6_addr *lcoa,
++                                  struct in6_addr *saddr,
++                                  struct in6_addr *fcoa,
++                                  struct mipv6_mh *mh)
++{
++      struct mipv6_mh_addr_ti *ti = (struct mipv6_mh_addr_ti *)mh->data;
++      int msg_len = (mh->length+1) << 3;
++      int opt_len;
++      DEBUG_FUNC();
++
++      if (msg_len > skb->len)
++              return -1;
++
++      opt_len = msg_len - sizeof(*mh) - sizeof(*ti);
++
++      if (opt_len < 0) {
++              __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw;
++              icmpv6_send(skb, ICMPV6_PARAMPROB,
++                          ICMPV6_HDR_FIELD, pos, skb->dev);
++
++              DEBUG(DBG_INFO, "Mobility Header length less than H/C TestInit");
++              return -1;
++      }
++      if (!mip6node_cnf.accept_ret_rout) {
++              DEBUG(DBG_INFO, "Return routability administratively disabled");
++              return -1;
++      }
++      if (lcoa || fcoa) {
++              DEBUG(DBG_INFO, "H/C TestInit has HAO or RTH2, dropped.");
++              return -1;
++      }
++
++      if (mh->type == MIPV6_MH_HOTI) {
++              MIPV6_INC_STATS(n_hoti_rcvd);
++              return mipv6_send_addr_test(cn, saddr, MIPV6_MH_HOT, ti);
++      } else if (mh->type == MIPV6_MH_COTI) {
++              MIPV6_INC_STATS(n_coti_rcvd);
++              return mipv6_send_addr_test(cn, saddr, MIPV6_MH_COT, ti);
++      } else 
++              return -1; /* Impossible to get here */
++}
++
++/**
++ * mipv6_handle_mh_bu - Binding Update handler
++ * @src: care-of address of sender
++ * @dst: our address
++ * @haddr: home address of sender
++ * @mh: pointer to the beginning of the Mobility Header
++ *
++ * Handles Binding Update. Packet and offset to option are passed.
++ * Returns 0 on success, otherwise negative.
++ **/
++static int mipv6_handle_mh_bu(struct sk_buff *skb,
++                            struct in6_addr *dst,
++                            struct in6_addr *unused,
++                            struct in6_addr *haddr, 
++                            struct in6_addr *coaddr,
++                            struct mipv6_mh *mh)
++{
++      struct mipv6_mh_bu *bu = (struct mipv6_mh_bu *)mh->data;
++      int msg_len = (mh->length+1) << 3;
++      int opt_len;
++      int auth = 0;
++      int dereg; /* Is this deregistration? */ 
++      int addr_type;
++
++      struct mipv6_bce bc_entry;
++      struct in6_addr *coa, *reply_coa;
++      __u8 *key_bu = NULL; /* RR BU authentication key */
++      __u8 flags = bu->flags;
++      __u16 sequence;
++      __u32 lifetime;
++      __u16 nonce_ind = (__u16) -1; 
++
++      if (msg_len > skb->len)
++              return -1;
++
++      opt_len = msg_len - sizeof(*mh) - sizeof(*bu);
++
++      if (opt_len < 0) {
++              __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw;
++              icmpv6_send(skb, ICMPV6_PARAMPROB,
++                          ICMPV6_HDR_FIELD, pos, skb->dev);
++
++              DEBUG(DBG_INFO, "Mobility Header length less than BU");
++              MIPV6_INC_STATS(n_bu_drop.invalid);
++              return -1;
++      }
++
++      addr_type = ipv6_addr_type(haddr);
++      if (addr_type&IPV6_ADDR_LINKLOCAL || !(addr_type&IPV6_ADDR_UNICAST))
++              return -EINVAL;
++
++      /* If HAO not present, CoA == HAddr */
++      if (coaddr == NULL) 
++              coa = haddr;
++      else {
++              coa = coaddr;
++              addr_type = ipv6_addr_type(coa);
++              if (addr_type&IPV6_ADDR_LINKLOCAL ||
++                  !(addr_type&IPV6_ADDR_UNICAST))
++                      return -EINVAL;
++      }
++      reply_coa = coa;
++
++      sequence = ntohs(bu->sequence);
++      if (bu->lifetime == 0xffff)
++              lifetime = 0xffffffff;
++      else
++              lifetime = ntohs(bu->lifetime) << 2;
++
++      dereg = (ipv6_addr_cmp(haddr, coa) == 0 || lifetime == 0);
++
++      if (opt_len > 0) {
++              struct mobopt opts;
++              memset(&opts, 0, sizeof(opts));
++              if (parse_mo_tlv(bu + 1, opt_len, &opts) < 0) {
++                      MIPV6_INC_STATS(n_bu_drop.invalid);
++                      return -1;
++              }
++              /*
++               * MIPV6_OPT_AUTH_DATA, MIPV6_OPT_NONCE_INDICES, 
++               * MIPV6_OPT_ALT_COA
++               */
++              if (opts.alt_coa) {
++                      coa = &opts.alt_coa->addr;
++                      dereg = (ipv6_addr_cmp(haddr, coa) == 0 || lifetime == 0);
++              }
++              addr_type = ipv6_addr_type(coa);
++              if (addr_type&IPV6_ADDR_LINKLOCAL || 
++                  !(addr_type&IPV6_ADDR_UNICAST))
++                      return -EINVAL;
++
++              if (flags & MIPV6_BU_F_HOME) {
++                      if (opts.nonce_indices)
++                              return -1;
++              } else {
++                      u8 ba_status = 0;
++                      u8 *h_ckie  = NULL, *c_ckie = NULL; /* Home and care-of cookies */
++
++                      /* BUs to CN MUST include authorization data and nonce indices options */
++                      if (!opts.auth_data || !opts.nonce_indices) {
++                              DEBUG(DBG_WARNING,
++                                    "Route optimization BU without authorization material, aborting processing");
++                              return MH_AUTH_FAILED;
++                      }
++                      if (mipv6_rr_cookie_create(
++                                  haddr, &h_ckie, opts.nonce_indices->home_nonce_i) < 0) {
++                              DEBUG(DBG_WARNING,
++                                    "mipv6_rr_cookie_create failed for home cookie");
++                              ba_status = EXPIRED_HOME_NONCE_INDEX;
++                      }
++                      nonce_ind = opts.nonce_indices->home_nonce_i;
++                      /* Don't create the care-of cookie, if MN deregisters */
++                      if (!dereg && mipv6_rr_cookie_create(
++                                  coa, &c_ckie,
++                                  opts.nonce_indices->careof_nonce_i) < 0) {
++                              DEBUG(DBG_WARNING,
++                                    "mipv6_rr_cookie_create failed for coa cookie");
++                              if (ba_status == 0)
++                                      ba_status = EXPIRED_CAREOF_NONCE_INDEX;
++                              else
++                                      ba_status = EXPIRED_NONCES;
++                      }
++                      if (ba_status == 0) {
++                              if (dereg)
++                                      key_bu = mipv6_rr_key_calc(h_ckie, NULL);
++                              else
++                                      key_bu = mipv6_rr_key_calc(h_ckie, c_ckie);            
++                              mh->checksum = 0;/* TODO: Don't mangle the packet */
++                              if (key_bu && mipv6_auth_check(
++                                      dst, coa, (__u8 *)mh,  msg_len + sizeof(*mh), opts.auth_data, key_bu) == 0) {
++                                      DEBUG(DBG_INFO, "mipv6_auth_check OK for BU");
++                                      auth = 1;
++                              } else {
++                                      DEBUG(DBG_WARNING, 
++                                            "BU Authentication failed");
++                              }
++                      }
++                      if (h_ckie)
++                              kfree(h_ckie);
++                      if (c_ckie)
++                              kfree(c_ckie);
++                      if (ba_status != 0) {
++                              MIPV6_INC_STATS(n_bu_drop.auth);
++                              mipv6_send_ba(dst, haddr, coa,
++                                            reply_coa, ba_status,
++                                            sequence, 0, NULL);
++                              goto out;
++                      }
++              }
++
++      }
++      /* Require authorization option for RO, home reg is protected by IPsec */
++      if (!(flags & MIPV6_BU_F_HOME) && !auth) {
++              MIPV6_INC_STATS(n_bu_drop.auth);
++              if (key_bu)
++                      kfree(key_bu);
++              return MH_AUTH_FAILED;
++      }
++
++      if (mipv6_bcache_get(haddr, dst, &bc_entry) == 0) {
++              if ((bc_entry.flags&MIPV6_BU_F_HOME) != 
++                  (flags&MIPV6_BU_F_HOME)) {
++                      DEBUG(DBG_INFO,
++                            "Registration type change. Sending BA REG_TYPE_CHANGE_FORBIDDEN");
++                      mipv6_send_ba(dst, haddr, coa, reply_coa,
++                                    REG_TYPE_CHANGE_FORBIDDEN,
++                                    sequence, lifetime, key_bu);
++                      goto out;
++              }
++              if (!MIPV6_SEQ_GT(sequence, bc_entry.seq)) {
++                      DEBUG(DBG_INFO,
++                            "Sequence number mismatch. Sending BA SEQUENCE_NUMBER_OUT_OF_WINDOW");
++                      mipv6_send_ba(dst, haddr, coa, reply_coa,
++                                    SEQUENCE_NUMBER_OUT_OF_WINDOW,
++                                    bc_entry.seq, lifetime, key_bu);
++                      goto out;
++              }
++      }
++
++      if (!dereg) {
++              int ifindex;
++              struct rt6_info *rt;
++
++              /* Avoid looping binding cache entries */
++              if (mipv6_bcache_get(coa, dst, &bc_entry) == 0) {
++                      DEBUG(DBG_WARNING, "Looped BU, dropping the packet");
++                      goto out;
++              }
++              DEBUG(DBG_INFO, "calling bu_add.");
++              if ((rt = rt6_lookup(haddr, dst, 0, 0)) != NULL) {
++                      ifindex = rt->rt6i_dev->ifindex;
++                      dst_release(&rt->u.dst);
++              } else {
++                      /*
++                       * Can't process the BU since the right interface is 
++                       * not found.
++                       */
++                      DEBUG(DBG_WARNING, "No route entry found for handling "
++                            "a BU request, (using 0 as index)");
++                      ifindex = 0;
++              }
++              if (flags & MIPV6_BU_F_HOME)
++                      mip6_fn.bce_home_add(ifindex, dst, haddr, coa, 
++                                           reply_coa, lifetime, sequence,
++                                           flags, key_bu);
++              else
++                      mip6_fn.bce_cache_add(ifindex, dst, haddr, coa, 
++                                            reply_coa, lifetime, sequence,
++                                            flags, key_bu);
++      } else {
++              DEBUG(DBG_INFO, "calling BCE delete.");
++
++              if (flags & MIPV6_BU_F_HOME)
++                      mip6_fn.bce_home_del(dst, haddr, coa, reply_coa,
++                                           sequence, flags, key_bu);
++              else {
++                      mipv6_rr_invalidate_nonce(nonce_ind);
++                      mip6_fn.bce_cache_del(dst, haddr, coa, reply_coa, 
++                                            sequence, flags, key_bu);
++              }
++      }
++ out:
++      MIPV6_INC_STATS(n_bu_rcvd);
++      if (key_bu)
++              kfree(key_bu);
++      return 0;
++}
++
++static int mipv6_mh_rcv(struct sk_buff *skb)
++{
++      struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
++      struct mipv6_mh *mh;
++      struct in6_addr *lhome, *fhome, *lcoa = NULL, *fcoa = NULL;
++      int ret = 0;
++
++      fhome = &skb->nh.ipv6h->saddr;
++      lhome = &skb->nh.ipv6h->daddr;
++
++      if (opt->hao != 0) {
++              struct mipv6_dstopt_homeaddr *hao;
++              hao = (struct mipv6_dstopt_homeaddr *)(skb->nh.raw + opt->hao);
++              fcoa = &hao->addr;
++      }
++
++      if (opt->srcrt2 != 0) {
++              struct rt2_hdr *rt2;
++              rt2 = (struct rt2_hdr *)((u8 *)skb->nh.raw + opt->srcrt2);
++              lcoa = &rt2->addr;
++      }
++
++      /* Verify checksum is correct */
++      if (skb->ip_summed == CHECKSUM_HW) {
++              skb->ip_summed = CHECKSUM_UNNECESSARY;
++              if (csum_ipv6_magic(fhome, lhome, skb->len, IPPROTO_MOBILITY,
++                                  skb->csum)) {
++                      if (net_ratelimit())
++                              printk(KERN_WARNING "MIPv6 MH hw checksum failed\n");
++                      skb->ip_summed = CHECKSUM_NONE;
++              }
++      }
++      if (skb->ip_summed == CHECKSUM_NONE) {
++              if (csum_ipv6_magic(fhome, lhome, skb->len, IPPROTO_MOBILITY,
++                                  skb_checksum(skb, 0, skb->len, 0))) {
++                      if (net_ratelimit())
++                              printk(KERN_WARNING "MIPv6 MH checksum failed\n");
++                      goto bad;
++              }
++      }
++
++      if (!pskb_may_pull(skb, skb->h.raw-skb->data+sizeof(*mh)) ||
++          !pskb_may_pull(skb, 
++                         skb->h.raw-skb->data+((skb->h.raw[1]+1)<<3))) {
++              DEBUG(DBG_INFO, "MIPv6 MH invalid length");
++              kfree_skb(skb);
++              return 0;
++      }
++
++      mh = (struct mipv6_mh *) skb->h.raw;
++
++      /* Verify there are no more headers after the MH */
++      if (mh->payload != NEXTHDR_NONE) {
++              __u32 pos = (__u32)&mh->payload - (__u32)skb->nh.raw;
++              icmpv6_send(skb, ICMPV6_PARAMPROB,
++                          ICMPV6_HDR_FIELD, pos, skb->dev);
++
++              DEBUG(DBG_INFO, "MIPv6 MH error");
++              goto bad;
++      }
++
++      if (mh->type > MIPV6_MH_MAX) {
++              /* send binding error */
++              printk("Invalid mobility header type (%d)\n", mh->type);
++              mipv6_send_be(lhome, fcoa ? fcoa : fhome,
++                            fcoa ? fhome : NULL, 
++                            MIPV6_BE_UNKNOWN_MH_TYPE);
++              goto bad;
++      }
++      if (mh_rcv[mh->type].func != NULL) {
++              ret = mh_rcv[mh->type].func(skb, lhome, lcoa, fhome, fcoa, mh);
++      } else {
++              DEBUG(DBG_INFO, "No handler for MH Type %d", mh->type);
++              goto bad;
++      }
++
++      kfree_skb(skb);
++      return 0;
++
++bad:
++      MIPV6_INC_STATS(n_mh_in_error);
++      kfree_skb(skb);
++      return 0;
++
++}
++
++#if LINUX_VERSION_CODE >= 0x2052a
++struct inet6_protocol mipv6_mh_protocol =
++{
++      mipv6_mh_rcv,           /* handler              */
++      NULL                    /* error control        */
++};
++#else
++struct inet6_protocol mipv6_mh_protocol = 
++{
++      mipv6_mh_rcv,           /* handler              */
++      NULL,                   /* error control        */
++      NULL,                   /* next                 */
++      IPPROTO_MOBILITY,       /* protocol ID          */
++      0,                      /* copy                 */
++      NULL,                   /* data                 */
++      "MIPv6 MH"              /* name                 */
++};
++#endif
++
++/*
++ *
++ * Code module init/exit functions
++ *
++ */
++
++int __init mipv6_mh_common_init(void)
++{
++      struct sock *sk;
++      int err;
++
++      mip6_fn.bce_home_add = bc_cn_home_add;
++      mip6_fn.bce_cache_add = bc_cache_add;
++      mip6_fn.bce_home_del = bc_cn_home_delete;
++      mip6_fn.bce_cache_del = bc_cache_delete;
++
++      mipv6_mh_socket = sock_alloc();
++      if (mipv6_mh_socket == NULL) {
++              printk(KERN_ERR
++                     "Failed to create the MIP6 MH control socket.\n");
++              return -1;
++      }
++      mipv6_mh_socket->type = SOCK_RAW;
++
++      if ((err = sock_create(PF_INET6, SOCK_RAW, IPPROTO_MOBILITY, 
++                             &mipv6_mh_socket)) < 0) {
++              printk(KERN_ERR
++                     "Failed to initialize the MIP6 MH control socket (err %d).\n",
++                     err);
++              sock_release(mipv6_mh_socket);
++              mipv6_mh_socket = NULL; /* for safety */
++              return err;
++      }
++
++      sk = mipv6_mh_socket->sk;
++      sk->allocation = GFP_ATOMIC;
++      sk->sndbuf = 64 * 1024 + sizeof(struct sk_buff);
++      sk->prot->unhash(sk);
++
++      memset(&mh_rcv, 0, sizeof(mh_rcv));
++      mh_rcv[MIPV6_MH_HOTI].func = mipv6_handle_mh_testinit;
++      mh_rcv[MIPV6_MH_COTI].func = mipv6_handle_mh_testinit;
++      mh_rcv[MIPV6_MH_BU].func =  mipv6_handle_mh_bu;
++
++#if LINUX_VERSION_CODE >= 0x2052a
++      if (inet6_add_protocol(&mipv6_mh_protocol, IPPROTO_MOBILITY) < 0) {
++              printk(KERN_ERR "Failed to register MOBILITY protocol\n");
++              sock_release(mipv6_mh_socket);
++              mipv6_mh_socket = NULL;
++              return -EAGAIN;
++      }
++#else
++      inet6_add_protocol(&mipv6_mh_protocol);
++#endif
++      /* To disable the use of dst_cache, 
++       *  which slows down the sending of BUs ??
++       */
++      sk->dst_cache=NULL; 
++
++      return 0;
++}
++
++void __exit mipv6_mh_common_exit(void)
++{
++      if (mipv6_mh_socket) sock_release(mipv6_mh_socket);
++      mipv6_mh_socket = NULL; /* For safety. */
++
++#if LINUX_VERSION_CODE >= 0x2052a
++      inet6_del_protocol(&mipv6_mh_protocol, IPPROTO_MOBILITY);
++#else
++      inet6_del_protocol(&mipv6_mh_protocol);
++#endif
++      memset(&mh_rcv, 0, sizeof(mh_rcv));
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/mobhdr_mn.c linux-2.4.25/net/ipv6/mobile_ip6/mobhdr_mn.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/mobhdr_mn.c   1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/mobhdr_mn.c       2004-06-26 11:29:32.000000000 +0100
+@@ -0,0 +1,1155 @@
++/*
++ *    Mobile IPv6 Mobility Header Functions for Mobile Node
++ *
++ *    Authors:
++ *    Antti Tuominen  <ajtuomin@tml.hut.fi>
++ *    Niklas Kämpe    <nhkampe@cc.hut.fi>
++ *    Henrik Petander <henrik.petander@hut.fi>
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ *
++ */
++
++#include <linux/types.h>
++#include <linux/sched.h>
++#include <linux/init.h>
++#include <net/ipv6.h>
++#include <net/addrconf.h>
++#include <net/mipv6.h>
++
++#include "mobhdr.h"
++#include "mn.h"
++#include "bul.h"
++#include "rr_crypto.h"
++#include "debug.h"
++#include "util.h"
++#include "stats.h"
++
++int rr_configured = 1;
++
++/* Return value of mipv6_rr_state() */
++#define NO_RR                 0
++#define DO_RR                 1
++#define RR_FOR_COA            2
++#define INPROGRESS_RR         3
++
++/** 
++ * send_bu_msg - sends a Binding Update 
++ * @bulentry : BUL entry with the information for building a BU
++ *
++ * Function builds a BU msg based on the contents of a bul entry.
++ * Does not change the bul entry.
++ **/
++static int send_bu_msg(struct mipv6_bul_entry *binding)
++{ 
++      int auth = 0; /* Use auth */
++      int ret = 0;
++      struct mipv6_auth_parm parm;
++      struct mipv6_mh_bu bu;
++
++      if (!binding) {
++              DEBUG(DBG_ERROR, "called with a null bul entry");
++              return -1;
++      }
++      
++      memset(&parm, 0, sizeof(parm));
++      if (mipv6_prefix_compare(&binding->coa, &binding->home_addr, 64))
++              parm.coa = &binding->home_addr;
++      else
++              parm.coa = &binding->coa;
++      parm.cn_addr = &binding->cn_addr;
++
++      if (binding->rr && binding->rr->kbu) {
++              DEBUG(DBG_INFO, "Binding with key");
++              auth = 1;
++              parm.k_bu = binding->rr->kbu;
++      }
++      memset(&bu, 0, sizeof(bu));
++      bu.flags = binding->flags;
++      bu.sequence = htons(binding->seq);
++      bu.lifetime = htons(binding->lifetime >> 2);
++      bu.reserved = 0;
++
++      ret = send_mh(&binding->cn_addr, &binding->home_addr,
++                    MIPV6_MH_BU, sizeof(bu), (u8 *)&bu, 
++                    &binding->home_addr, NULL, 
++                    binding->ops, &parm);
++
++      if (ret == 0)
++              MIPV6_INC_STATS(n_bu_sent);
++
++      return ret;
++}
++
++/**
++ * mipv6_send_addr_test_init - send a HoTI or CoTI message
++ * @saddr: source address for H/CoTI
++ * @daddr: destination address for H/CoTI
++ * @msg_type: Identifies whether HoTI or CoTI
++ * @init_cookie: the HoTi or CoTi init cookie
++ *
++ * The message will be retransmitted till we get a HoT or CoT message, since 
++ * our caller (mipv6_RR_start) has entered this message in the BUL with
++ * exponential backoff retramission set.
++ */
++static int mipv6_send_addr_test_init(struct in6_addr *saddr,
++                                   struct in6_addr *daddr,
++                                   u8 msg_type,
++                                   u8 *init_cookie)
++{
++      struct mipv6_mh_addr_ti ti;
++      struct mipv6_mh_opt *ops = NULL;
++      int ret = 0;
++
++      /* Set reserved and copy the cookie from address test init msg */
++      ti.reserved = 0;
++      mipv6_rr_mn_cookie_create(init_cookie);
++      memcpy(ti.init_cookie, init_cookie, MIPV6_RR_COOKIE_LENGTH);
++
++      ret = send_mh(daddr, saddr, msg_type, sizeof(ti), (u8 *)&ti,
++                    NULL, NULL, ops, NULL);
++      if (ret == 0) {
++              if (msg_type == MIPV6_MH_HOTI) {
++                      MIPV6_INC_STATS(n_hoti_sent);
++              } else {
++                      MIPV6_INC_STATS(n_coti_sent);
++              }
++      }
++
++      return ret;
++}
++
++/*
++ *
++ * Callback handlers for binding update list
++ *
++ */
++
++/* Return value 0 means keep entry, non-zero means discard entry. */
++
++/* Callback for BUs not requiring acknowledgement
++ */
++int bul_entry_expired(struct mipv6_bul_entry *bulentry)
++{
++      /* Lifetime expired, delete entry. */
++      DEBUG(DBG_INFO, "bul entry 0x%p lifetime expired, deleting entry", 
++            bulentry);
++      return 1;
++}
++
++/* Callback for BUs requiring acknowledgement with exponential resending
++ * scheme */
++static int bul_resend_exp(struct mipv6_bul_entry *bulentry)
++{
++      unsigned long now = jiffies;
++      
++      DEBUG(DBG_INFO, "(0x%x) resending bu", (int) bulentry);
++
++      
++      /* If sending a de-registration, do not care about the
++       * lifetime value, as de-registrations are normally sent with
++       * a zero lifetime value. If the entry is a home entry get the 
++       * current lifetime. 
++       */
++
++      if (bulentry->lifetime != 0) {
++              bulentry->lifetime = mipv6_mn_get_bulifetime(
++                      &bulentry->home_addr, &bulentry->coa, bulentry->flags);
++
++              bulentry->expire = now + bulentry->lifetime * HZ;
++      } else {
++              bulentry->expire = now + HOME_RESEND_EXPIRE * HZ; 
++      }
++      if (bulentry->rr) {
++              /* Redo RR, if cookies have expired */
++              if (time_after(jiffies, bulentry->rr->home_time + MAX_TOKEN_LIFE * HZ)) 
++                      bulentry->rr->rr_state |= RR_WAITH;
++              if (time_after(jiffies, bulentry->rr->careof_time + MAX_NONCE_LIFE * HZ)) 
++                      bulentry->rr->rr_state |= RR_WAITC;
++
++              if (bulentry->rr->rr_state & RR_WAITH) {
++                      /* Resend HoTI directly */
++                      mipv6_send_addr_test_init(&bulentry->home_addr, 
++                                                &bulentry->cn_addr, MIPV6_MH_HOTI,
++                                                bulentry->rr->hot_cookie);
++              }
++              if (bulentry->rr->rr_state & RR_WAITC) {
++                              /* Resend CoTI directly */
++                              mipv6_send_addr_test_init(&bulentry->coa, 
++                                                        &bulentry->cn_addr, MIPV6_MH_COTI,
++                                                        bulentry->rr->cot_cookie);
++                      }
++              goto out;
++      }
++      
++      bulentry->seq++;
++
++      if (send_bu_msg(bulentry) < 0)
++              DEBUG(DBG_ERROR, "Resending of BU failed");
++
++out:
++      /* Schedule next retransmission */
++      if (bulentry->delay < bulentry->maxdelay) {
++              bulentry->delay = 2 * bulentry->delay;
++              if (bulentry->delay > bulentry->maxdelay) {
++                      /* can happen if maxdelay is not power(mindelay, 2) */
++                      bulentry->delay = bulentry->maxdelay;
++              }
++      } else if (bulentry->flags & MIPV6_BU_F_HOME) {
++              /* Home registration - continue sending BU at maxdelay rate */
++              DEBUG(DBG_INFO, "Sending BU to HA after max ack wait time "
++                    "reached(0x%x)", (int) bulentry);
++              bulentry->delay = bulentry->maxdelay;
++      } else if (!(bulentry->flags & MIPV6_BU_F_HOME)) {
++              /* Failed to get BA from a CN */
++              bulentry->callback_time = now;
++              return -1;
++      }
++      
++      bulentry->callback_time = now + bulentry->delay * HZ;
++      return 0;
++}
++
++
++
++/* Callback for sending a registration refresh BU
++ */
++static int bul_refresh(struct mipv6_bul_entry *bulentry)
++{
++      unsigned long now = jiffies;
++      
++      /* Refresh interval passed, send new BU */
++      DEBUG(DBG_INFO, "bul entry 0x%x refresh interval passed, sending new BU", (int) bulentry);
++      if (bulentry->lifetime == 0)
++              return 0;
++
++      /* Set new maximum lifetime and expiration time */
++      bulentry->lifetime = mipv6_mn_get_bulifetime(&bulentry->home_addr, 
++                                                   &bulentry->coa, 
++                                                   bulentry->flags);
++      bulentry->expire = now + bulentry->lifetime * HZ;
++      bulentry->seq++;
++      /* Send update */
++      if (send_bu_msg(bulentry) < 0)
++              DEBUG(DBG_ERROR, "Resending of BU failed");
++      
++      if (time_after_eq(now, bulentry->expire)) {
++              /* Sanity check */
++              DEBUG(DBG_ERROR, "bul entry expire time in history - setting expire to %u secs", ERROR_DEF_LIFETIME);
++              bulentry->lifetime = ERROR_DEF_LIFETIME;
++              bulentry->expire = now + ERROR_DEF_LIFETIME*HZ;
++      }
++
++      /* Set up retransmission */
++      bulentry->state = RESEND_EXP;
++      bulentry->callback = bul_resend_exp;
++      bulentry->callback_time = now + INITIAL_BINDACK_TIMEOUT*HZ;
++      bulentry->delay = INITIAL_BINDACK_TIMEOUT;
++      bulentry->maxdelay = MAX_BINDACK_TIMEOUT;
++
++      return 0;
++}
++
++static int mipv6_send_RR_bu(struct mipv6_bul_entry *bulentry)
++{
++      int ret;
++      int ops_len = 0;
++      u16 nonces[2];
++
++      DEBUG(DBG_INFO, "Sending BU to CN  %x:%x:%x:%x:%x:%x:%x:%x "
++            "for home address %x:%x:%x:%x:%x:%x:%x:%x", 
++            NIPV6ADDR(&bulentry->cn_addr), NIPV6ADDR(&bulentry->home_addr));
++      nonces[0] = bulentry->rr->home_nonce_index;
++      nonces[1] = bulentry->rr->careof_nonce_index;
++      ops_len = sizeof(struct mipv6_mo_bauth_data) + MIPV6_RR_MAC_LENGTH + 
++                      sizeof(struct mipv6_mo_nonce_indices);
++      if (bulentry->ops) {
++              DEBUG(DBG_WARNING, "Bul entry had existing mobility options, freeing them");
++              kfree(bulentry->ops);
++      }
++      bulentry->ops = alloc_mh_opts(ops_len);
++
++      if (!bulentry->ops)
++              return -ENOMEM;
++      if (append_mh_opt(bulentry->ops, MIPV6_OPT_NONCE_INDICES, 
++                        sizeof(struct mipv6_mo_nonce_indices) - 2, nonces) < 0)
++              return -ENOMEM;
++
++      if (append_mh_opt(bulentry->ops, MIPV6_OPT_AUTH_DATA,
++                        MIPV6_RR_MAC_LENGTH, NULL) < 0)
++              return -ENOMEM;
++      /* RR procedure is over, send a BU */
++      if (!(bulentry->flags & MIPV6_BU_F_ACK)) {
++              DEBUG(DBG_INFO, "Setting bul callback to bul_entry_expired");
++              bulentry->state = ACK_OK;
++              bulentry->callback = bul_entry_expired;
++              bulentry->callback_time = jiffies + HZ * bulentry->lifetime;
++              bulentry->expire = jiffies + HZ *  bulentry->lifetime;
++      }
++      else {
++              bulentry->callback_time = jiffies + HZ;
++              bulentry->expire = jiffies + HZ *  bulentry->lifetime;
++      }
++
++      ret  = send_bu_msg(bulentry);
++      mipv6_bul_reschedule(bulentry);
++      return ret;
++}
++
++static int mipv6_rr_state(struct mipv6_bul_entry *bul, struct in6_addr *saddr,
++                        struct in6_addr *coa, __u8 flags)
++{
++      if (!rr_configured)
++              return NO_RR;
++              if (flags & MIPV6_BU_F_HOME) {
++              /* We don't need RR, this is a Home Registration */
++              return NO_RR;
++      }
++      if (!bul || !bul->rr) {
++              /* First time BU to CN, need RR */
++              return DO_RR;
++      }
++
++      switch (bul->rr->rr_state) {
++      case RR_INIT:
++              /* Need RR if first BU to CN */
++              return DO_RR;
++      case RR_DONE:
++              /* If MN moves to a new coa, do RR for it */
++              if (!ipv6_addr_cmp(&bul->coa, coa))  
++                      return NO_RR; 
++              else
++                      return DO_RR;
++      default:
++              /*
++               * We are in the middle of RR, the HoTI and CoTI have been
++               * sent. But we haven't got HoT and CoT from the CN, so
++               * don't do anything more at this time.
++               */
++              return INPROGRESS_RR;
++      }
++}
++
++/**
++ * mipv6_RR_start - Start Return Routability procedure
++ * @home_addr: home address
++ * @cn_addr: correspondent address
++ * @coa: care-of address
++ * @entry: binding update list entry (if any)
++ * @initdelay: initial ack timeout
++ * @maxackdelay: maximum ack timeout
++ * @flags: flags
++ * @lifetime: lifetime of binding
++ * @ops: mobility options
++ *
++ * Caller must hold @bul_lock (write).
++ **/
++static int mipv6_RR_start(struct in6_addr *home_addr, struct in6_addr *cn_addr,
++                        struct in6_addr *coa, struct mipv6_bul_entry *entry,
++                        __u32 initdelay, __u32 maxackdelay, __u8 flags, 
++                        __u32 lifetime, struct mipv6_mh_opt *ops)
++{
++      int ret = -1;
++      struct mipv6_bul_entry *bulentry = entry;
++      struct mipv6_rr_info *rr = NULL;
++      int seq = 0;
++      DEBUG_FUNC();
++      
++      /* Do RR procedure only for care-of address after handoff, 
++         if home cookie is still valid */
++      if (bulentry && bulentry->rr) {
++              if (time_before(jiffies, bulentry->rr->home_time + MAX_NONCE_LIFE * HZ) &&
++                  lifetime && !(ipv6_addr_cmp(home_addr, coa) == 0)) { 
++                      mipv6_rr_mn_cookie_create(bulentry->rr->cot_cookie); 
++                      DEBUG(DBG_INFO, "Bul entry and rr info exist, only doing RR for CoA");
++                      ipv6_addr_copy(&bulentry->coa, coa);
++                      bulentry->rr->rr_state |= RR_WAITC;
++              } else if (!lifetime) { /* Send only HoTi when returning home */
++                      mipv6_rr_mn_cookie_create(bulentry->rr->hot_cookie); 
++                      DEBUG(DBG_INFO, "Bul entry and rr info exist, only doing RR for HoA");
++                      ipv6_addr_copy(&bulentry->coa, coa); /* Home address as CoA */
++                      bulentry->rr->rr_state |= RR_WAITH;
++              }
++      } else {
++              DEBUG(DBG_INFO, "Doing RR for both HoA and CoA");
++              rr = kmalloc(sizeof(*rr), GFP_ATOMIC);
++              memset(rr, 0, sizeof(*rr));
++              rr->rr_state = RR_WAITHC;
++      } 
++      if (bulentry) {
++              if (bulentry->state == ACK_ERROR)
++                      goto out;
++              seq = bulentry->seq + 1;
++      } else
++              seq = 0;
++      /* Save the info in the BUL to retransmit the BU after RR is done */
++      /* Caller must hold bul_lock (write) since we don't */
++       
++      if ((bulentry = mipv6_bul_add(cn_addr, home_addr, coa, 
++                                    min_t(__u32, lifetime, MAX_RR_BINDING_LIFE),
++                                    seq, flags, bul_resend_exp, initdelay, 
++                                    RESEND_EXP, initdelay, 
++                                    maxackdelay, ops, 
++                                    rr)) == NULL) {
++              DEBUG(DBG_INFO, "couldn't update BUL for HoTi");
++              goto out;
++      }
++
++      rr = bulentry->rr; 
++      if (rr->rr_state&RR_WAITH)
++              mipv6_send_addr_test_init(home_addr, cn_addr, MIPV6_MH_HOTI, 
++                                        rr->hot_cookie);
++      if (ipv6_addr_cmp(home_addr, coa) && lifetime) 
++              mipv6_send_addr_test_init(coa, cn_addr, MIPV6_MH_COTI, rr->cot_cookie);
++      else {
++              bulentry->rr->rr_state &= ~RR_WAITC;
++      }
++      ret = 0;
++out:
++      return ret;
++}
++
++/*
++ * Status codes for mipv6_ba_rcvd()
++ */
++#define STATUS_UPDATE 0
++#define STATUS_REMOVE 1
++
++/**
++ * mipv6_ba_rcvd - Update BUL for this Binding Acknowledgement
++ * @ifindex: interface BA came from
++ * @cnaddr: sender IPv6 address
++ * @home_addr: home address
++ * @sequence: sequence number
++ * @lifetime: lifetime granted by Home Agent in seconds
++ * @refresh: recommended resend interval
++ * @status: %STATUS_UPDATE (ack) or %STATUS_REMOVE (nack)
++ *
++ * This function must be called to notify the module of the receipt of
++ * a binding acknowledgement so that it can cease retransmitting the
++ * option. The caller must have validated the acknowledgement before calling
++ * this function. 'status' can be either STATUS_UPDATE in which case the
++ * binding acknowledgement is assumed to be valid and the corresponding
++ * binding update list entry is updated, or STATUS_REMOVE in which case
++ * the corresponding binding update list entry is removed (this can be
++ * used upon receiving a negative acknowledgement).
++ * Returns 0 if a matching binding update has been sent or non-zero if
++ * not.
++ */
++static int mipv6_ba_rcvd(int ifindex, struct in6_addr *cnaddr, 
++                       struct in6_addr *home_addr, 
++                       u16 sequence, u32 lifetime, 
++                       u32 refresh, int status)
++{
++      struct mipv6_bul_entry *bulentry;
++      unsigned long now = jiffies;
++      struct in6_addr coa;
++
++      DEBUG(DBG_INFO, "BA received with sequence number 0x%x, status: %d",
++            (int) sequence, status);
++
++      /* Find corresponding entry in binding update list. */
++      write_lock(&bul_lock);
++      if ((bulentry = mipv6_bul_get(cnaddr, home_addr)) == NULL) {
++              DEBUG(DBG_INFO, "- discarded, no entry in bul matches BA source address");
++              write_unlock(&bul_lock);
++              return -1;
++      }
++      
++      ipv6_addr_copy(&coa, &bulentry->coa); 
++      if (status == SEQUENCE_NUMBER_OUT_OF_WINDOW) {
++              __u32 lifetime = mipv6_mn_get_bulifetime(&bulentry->home_addr, 
++                                                       &bulentry->coa, 
++                                                       bulentry->flags);
++              bulentry->seq = sequence;
++
++              mipv6_send_bu(&bulentry->home_addr, &bulentry->cn_addr, 
++                            &bulentry->coa, INITIAL_BINDACK_TIMEOUT,
++                            MAX_BINDACK_TIMEOUT, 1, bulentry->flags,
++                            lifetime, NULL);
++              write_unlock(&bul_lock);
++              return 0;
++      } else if (status >= REASON_UNSPECIFIED) {
++              int err;
++              int at_home = MN_NOT_AT_HOME;
++              DEBUG(DBG_WARNING, "- NACK - BA status:  %d, deleting bul entry", status);
++              if (bulentry->flags & MIPV6_BU_F_HOME) {
++                      struct mn_info *minfo;
++                      read_lock(&mn_info_lock);
++                      minfo = mipv6_mninfo_get_by_home(home_addr);
++                      if (minfo) {
++                              spin_lock(&minfo->lock);
++                              if (minfo->is_at_home != MN_NOT_AT_HOME)
++                                      minfo->is_at_home = MN_AT_HOME;
++                              at_home = minfo->is_at_home;
++                              minfo->has_home_reg = 0;
++                              spin_unlock(&minfo->lock);
++                      }
++                      read_unlock(&mn_info_lock);
++                      DEBUG(DBG_ERROR, "Home registration failed: BA status:  %d, deleting bul entry", status);
++              }
++              write_unlock(&bul_lock);
++              err = mipv6_bul_delete(cnaddr, home_addr);
++              if (at_home == MN_AT_HOME) {
++                      mipv6_mn_send_home_na(home_addr);
++                      write_lock_bh(&bul_lock);
++                      mipv6_bul_iterate(mn_cn_handoff, &coa);
++                      write_unlock_bh(&bul_lock);
++              }
++              return err;
++      }
++      bulentry->state = ACK_OK;
++
++      if (bulentry->flags & MIPV6_BU_F_HOME && lifetime > 0) {
++              /* For home registrations: schedule a refresh binding update.
++               * Use the refresh interval given by home agent or 80%
++               * of lifetime, whichever is less.
++               *
++               * Adjust binding lifetime if 'granted' lifetime
++               * (lifetime value in received binding acknowledgement)
++               * is shorter than 'requested' lifetime (lifetime
++               * value sent in corresponding binding update).
++               * max((L_remain - (L_update - L_ack)), 0)
++               */
++              if (lifetime * HZ < (bulentry->expire - bulentry->lastsend)) {
++                      bulentry->expire = 
++                              max_t(__u32, bulentry->expire - 
++                                    ((bulentry->expire - bulentry->lastsend) - 
++                                     lifetime * HZ), jiffies + 
++                                    ERROR_DEF_LIFETIME * HZ);
++              }
++              if (refresh > lifetime || refresh == 0)
++                      refresh = 4 * lifetime / 5;
++                      DEBUG(DBG_INFO, "setting callback for expiration of"
++                            " a Home Registration: lifetime:%d, refresh:%d",
++                            lifetime, refresh);
++              bulentry->callback = bul_refresh;
++              bulentry->callback_time = now + refresh * HZ;
++              bulentry->expire = now + lifetime * HZ;
++              bulentry->lifetime = lifetime;
++              if (time_after_eq(jiffies, bulentry->expire)) {
++                      /* Sanity check */
++                      DEBUG(DBG_ERROR, "bul entry expire time in history - setting expire to %u secs",
++                            ERROR_DEF_LIFETIME);
++                      bulentry->expire = jiffies + ERROR_DEF_LIFETIME * HZ;
++              }
++              mipv6_mn_set_home_reg(home_addr, 1);
++              mipv6_bul_iterate(mn_cn_handoff, &coa);
++      } else if ((bulentry->flags & MIPV6_BU_F_HOME) && bulentry->lifetime == 0) {
++              write_unlock(&bul_lock);
++              DEBUG(DBG_INFO, "Got BA for deregistration BU");
++              mipv6_mn_set_home_reg(home_addr, 0);
++              mipv6_bul_delete(cnaddr, home_addr);
++              mipv6_mn_send_home_na(home_addr);
++
++              write_lock_bh(&bul_lock);
++              mipv6_bul_iterate(mn_cn_handoff, &coa);
++              write_unlock_bh(&bul_lock);
++              return 0;
++      }
++
++      mipv6_bul_reschedule(bulentry);
++      write_unlock(&bul_lock);
++
++      return 0;
++}
++
++static int mipv6_handle_mh_HC_test(struct sk_buff *skb,
++                                 struct in6_addr *saddr,
++                                 struct in6_addr *fcoa,
++                                 struct in6_addr *cn,
++                                 struct in6_addr *lcoa,
++                                 struct mipv6_mh *mh)
++{
++      int ret = 0;
++      int msg_len = (mh->length+1) << 3;
++      int opt_len;
++
++      struct mipv6_mh_addr_test *tm = (struct mipv6_mh_addr_test *)mh->data;
++      struct mipv6_bul_entry *bulentry;
++
++      DEBUG_FUNC();
++
++      if (msg_len > skb->len)
++              return -1;
++
++      opt_len = msg_len - sizeof(*mh) - sizeof(*tm);
++
++      if (opt_len < 0) {
++              __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw;
++              icmpv6_send(skb, ICMPV6_PARAMPROB,
++                          ICMPV6_HDR_FIELD, pos, skb->dev);
++
++              DEBUG(DBG_INFO, "Mobility Header length less than H/C Test");
++              return -1;
++      }
++      if (fcoa || lcoa) {
++              DEBUG(DBG_INFO, "H/C Test has HAO or RTH2, dropped.");
++              return -1;
++      }
++      write_lock(&bul_lock);
++
++      /* We need to get the home address, since CoT only has the CoA*/
++      if (mh->type == MIPV6_MH_COT) {
++              if ((bulentry = mipv6_bul_get_by_ccookie(cn, tm->init_cookie)) == NULL) {
++                      DEBUG(DBG_ERROR, "has no BUL or RR state for "
++                            "source:%x:%x:%x:%x:%x:%x:%x:%x",
++                            NIPV6ADDR(cn));
++                      write_unlock(&bul_lock);
++                      return -1;
++              }
++      } else { /* HoT has the home address */
++              if (((bulentry = mipv6_bul_get(cn, saddr)) == NULL) || !bulentry->rr) {
++                      DEBUG(DBG_ERROR, "has no BUL or RR state for "
++                            "source:%x:%x:%x:%x:%x:%x:%x:%x "
++                            "dest:%x:%x:%x:%x:%x:%x:%x:%x",
++                            NIPV6ADDR(cn), NIPV6ADDR(saddr));
++                      write_unlock(&bul_lock);
++                      return -1;
++              }
++      }
++
++      switch (mh->type) {
++      case MIPV6_MH_HOT:
++              if ((bulentry->rr->rr_state & RR_WAITH) == 0) {
++                      DEBUG(DBG_ERROR, "Not waiting for a Home Test message");
++                      goto out;
++              }
++              /*
++               * Make sure no home cookies have been received yet.
++               * TODO: Check not being put in at this time since subsequent
++               * BU's after this time will have home cookie stored.
++               */
++      
++              /* Check if the cookie received is the right one */
++              if (!mipv6_equal_cookies(tm->init_cookie,
++                                       bulentry->rr->hot_cookie)) {
++                      /* Invalid cookie, might be an old cookie */
++                      DEBUG(DBG_WARNING, "Received HoT cookie does not match stored cookie");
++                      goto out;
++              }
++              DEBUG(DBG_INFO, "Got Care-of Test message");
++              bulentry->rr->rr_state &= ~RR_WAITH;
++              memcpy(bulentry->rr->home_cookie, tm->kgen_token, MIPV6_COOKIE_LEN);
++              bulentry->rr->home_nonce_index = tm->nonce_index;
++              bulentry->rr->home_time = jiffies;
++              ret = 1;
++              break;
++
++      case MIPV6_MH_COT:
++              if ((bulentry->rr->rr_state & RR_WAITC) == 0) {
++                      DEBUG(DBG_ERROR, "Not waiting for a Home Test message");
++                      goto out;
++              }
++              /*
++               * Make sure no home cookies have been received yet.
++               * TODO: Check not being put in at this time since subsequent
++               * BU's at this time will have careof cookie stored.
++               */
++      
++              /* Check if the cookie received is the right one */
++              if (!mipv6_equal_cookies(tm->init_cookie,
++                                       bulentry->rr->cot_cookie)) {
++                      DEBUG(DBG_INFO, "Received CoT cookie does not match stored cookie");
++                      goto out;
++              }
++              bulentry->rr->rr_state &= ~RR_WAITC;
++              memcpy(bulentry->rr->careof_cookie, tm->kgen_token, MIPV6_COOKIE_LEN);
++              bulentry->rr->careof_nonce_index = tm->nonce_index;
++              bulentry->rr->careof_time = jiffies;
++              ret = 1;
++              break;
++      default:
++              /* Impossible to get here */
++              break;
++      }
++out:
++      if (bulentry->rr->rr_state == RR_DONE) {
++              if (bulentry->rr->kbu) /* First free any old keys */
++                      kfree(bulentry->rr->kbu);
++              /* Store the session key to be used in BU's */
++              if (ipv6_addr_cmp(&bulentry->coa, &bulentry->home_addr) && bulentry->lifetime)
++                      bulentry->rr->kbu = mipv6_rr_key_calc(bulentry->rr->home_cookie,
++                                                            bulentry->rr->careof_cookie);
++              else 
++                      bulentry->rr->kbu = mipv6_rr_key_calc(bulentry->rr->home_cookie,
++                                                            NULL);
++              /* RR procedure is over, send a BU */
++              mipv6_send_RR_bu(bulentry);
++      }
++      write_unlock(&bul_lock);
++      return ret;
++}
++
++/**
++ * mipv6_handle_mh_brr - Binding Refresh Request handler
++ * @home: home address
++ * @coa: care-of address
++ * @cn: source of this packet
++ * @mh: pointer to the beginning of the Mobility Header
++ *
++ * Handles Binding Refresh Request.  Packet and offset to option are
++ * passed.  Returns 0 on success, otherwise negative.
++ **/
++static int mipv6_handle_mh_brr(struct sk_buff *skb,
++                             struct in6_addr *home,
++                             struct in6_addr *unused1,
++                             struct in6_addr *cn,
++                             struct in6_addr *unused2,
++                             struct mipv6_mh *mh)
++{
++      struct mipv6_mh_brr *brr = (struct mipv6_mh_brr *)mh->data;
++      struct mipv6_bul_entry *binding;
++      int msg_len = (mh->length+1) << 3;
++      int opt_len;
++
++      if (msg_len > skb->len)
++              return -1;
++
++      opt_len = msg_len - sizeof(*mh) - sizeof(*brr);
++
++      if (opt_len < 0) {
++              __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw;
++              icmpv6_send(skb, ICMPV6_PARAMPROB,
++                          ICMPV6_HDR_FIELD, pos, skb->dev);
++
++              DEBUG(DBG_WARNING, "Mobility Header length less than BRR");
++              MIPV6_INC_STATS(n_brr_drop.invalid);
++              return -1;
++      }
++
++      /* check we know src, else drop */
++      write_lock(&bul_lock);
++      if ((binding = mipv6_bul_get(cn, home)) == NULL) {
++              MIPV6_INC_STATS(n_brr_drop.misc);
++              write_unlock(&bul_lock);
++              return MH_UNKNOWN_CN;
++      }
++
++      MIPV6_INC_STATS(n_brr_rcvd);
++
++      if (opt_len > 0) {
++              struct mobopt opts;
++              memset(&opts, 0, sizeof(opts));
++              if (parse_mo_tlv(brr + 1, opt_len, &opts) < 0) {
++                      write_unlock(&bul_lock);
++                      return -1;
++              }
++              /*
++               * MIPV6_OPT_AUTH_DATA
++               */
++      }
++
++      /* must hold bul_lock (write) */
++      mipv6_RR_start(home, cn, &binding->coa, binding, binding->delay, 
++                     binding->maxdelay, binding->flags,
++                     binding->lifetime, binding->ops);
++
++      write_unlock(&bul_lock);
++      /* MAY also decide to delete binding and send zero lifetime BU
++           with alt-coa set to home address */
++
++      return 0;
++}
++
++/**
++ * mipv6_handle_mh_ba - Binding Acknowledgement handler
++ * @src: source of this packet
++ * @coa: care-of address
++ * @home: home address
++ * @mh: pointer to the beginning of the Mobility Header
++ *
++ **/
++static int mipv6_handle_mh_ba(struct sk_buff *skb,
++                            struct in6_addr *home,
++                            struct in6_addr *coa,
++                            struct in6_addr *src,
++                            struct in6_addr *unused,
++                            struct mipv6_mh *mh)
++{
++      struct mipv6_mh_ba *ba = (struct mipv6_mh_ba *)mh->data;
++      struct mipv6_bul_entry *binding = NULL;
++      struct mobopt opts;
++      int msg_len = (mh->length+1) << 3;
++      int opt_len;
++
++      int auth = 1, req_auth = 1, refresh = -1, ifindex = 0;
++      u32 lifetime, sequence;
++
++      if (msg_len > skb->len)
++              return -1;
++
++      opt_len = msg_len - sizeof(*mh) - sizeof(*ba);
++
++      if (opt_len < 0) {
++              __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw;
++              icmpv6_send(skb, ICMPV6_PARAMPROB,
++                          ICMPV6_HDR_FIELD, pos, skb->dev);
++
++              DEBUG(DBG_WARNING, "Mobility Header length less than BA");
++              MIPV6_INC_STATS(n_ba_drop.invalid);
++              return -1;
++      }
++
++      lifetime = ntohs(ba->lifetime) << 2;
++      sequence = ntohs(ba->sequence);
++
++      if (opt_len > 0) {
++              memset(&opts, 0, sizeof(opts));
++              if (parse_mo_tlv(ba + 1, opt_len, &opts) < 0)
++                      return -1;
++              /*
++               * MIPV6_OPT_AUTH_DATA, MIPV6_OPT_BR_ADVICE
++               */
++              if (opts.br_advice)
++                      refresh = ntohs(opts.br_advice->refresh_interval);
++      }
++
++      if (ba->status >= EXPIRED_HOME_NONCE_INDEX && 
++          ba->status <= EXPIRED_NONCES) 
++              req_auth = 0;
++      
++      write_lock(&bul_lock);
++      binding = mipv6_bul_get(src, home);
++      if (!binding) {
++              DEBUG(DBG_INFO, "No binding, BA dropped.");
++              write_unlock(&bul_lock);
++              return -1;
++      }
++
++      if (opts.auth_data && binding->rr && 
++          (mipv6_auth_check(src, coa, (__u8 *)mh, msg_len, 
++                            opts.auth_data, binding->rr->kbu) == 0))
++              auth = 1;
++
++      if (req_auth && binding->rr && !auth) {
++              DEBUG(DBG_INFO, "BA Authentication failed.");
++              MIPV6_INC_STATS(n_ba_drop.auth);
++              write_unlock(&bul_lock);
++              return MH_AUTH_FAILED;
++      }
++
++      if (ba->status == SEQUENCE_NUMBER_OUT_OF_WINDOW) {
++              DEBUG(DBG_INFO,
++                    "Sequence number out of window, setting seq to %d",
++                    sequence);
++      } else if (binding->seq != sequence) {
++              DEBUG(DBG_INFO, "BU/BA Sequence Number mismatch %d != %d",
++                    binding->seq, sequence);
++              MIPV6_INC_STATS(n_ba_drop.invalid);
++              write_unlock(&bul_lock);
++              return MH_SEQUENCE_MISMATCH;
++      }
++      if (ba->status == EXPIRED_HOME_NONCE_INDEX || ba->status == EXPIRED_NONCES) {
++              if (binding->rr) {
++                      /* Need to resend home test init to CN */
++                      binding->rr->rr_state |= RR_WAITH;
++                      mipv6_send_addr_test_init(&binding->home_addr, 
++                                                &binding->cn_addr, 
++                                                MIPV6_MH_HOTI,
++                                                binding->rr->hot_cookie);
++                      MIPV6_INC_STATS(n_ban_rcvd);
++              } else {
++                      DEBUG(DBG_WARNING, "Got BA with status EXPIRED_HOME_NONCE_INDEX"
++                            "for non-RR BU");
++                      MIPV6_INC_STATS(n_ba_drop.invalid);
++              }
++              write_unlock(&bul_lock);
++              return 0;
++      } 
++      if (ba->status == EXPIRED_CAREOF_NONCE_INDEX || ba->status == EXPIRED_NONCES) {
++              if (binding->rr) { 
++                      /* Need to resend care-of test init to CN */
++                      binding->rr->rr_state |= RR_WAITC;
++                      mipv6_send_addr_test_init(&binding->coa, 
++                                                &binding->cn_addr, 
++                                                MIPV6_MH_COTI,
++                                                binding->rr->cot_cookie);
++                      MIPV6_INC_STATS(n_ban_rcvd);
++              } else  {
++                      DEBUG(DBG_WARNING, "Got BA with status EXPIRED_HOME_CAREOF_INDEX"
++                            "for non-RR BU");
++                      MIPV6_INC_STATS(n_ba_drop.invalid);
++              }
++              write_unlock(&bul_lock);
++              return 0;
++      }
++      write_unlock(&bul_lock);
++      
++      if (ba->status >= REASON_UNSPECIFIED) {
++              DEBUG(DBG_INFO, "Binding Ack status : %d indicates error", ba->status);
++              mipv6_ba_rcvd(ifindex, src, home, sequence, lifetime,
++                            refresh, ba->status);
++              MIPV6_INC_STATS(n_ban_rcvd);
++              return 0;
++      }
++      MIPV6_INC_STATS(n_ba_rcvd);
++      if (mipv6_ba_rcvd(ifindex, src, home, ntohs(ba->sequence), lifetime,
++                        refresh, ba->status)) {
++              DEBUG(DBG_WARNING, "mipv6_ba_rcvd failed");
++      }
++      
++      return 0;
++}
++
++/**
++ * mipv6_handle_mh_be - Binding Error handler
++ * @cn: source of this packet
++ * @coa: care-of address
++ * @home: home address
++ * @mh: pointer to the beginning of the Mobility Header
++ *
++ **/
++
++static int mipv6_handle_mh_be(struct sk_buff *skb,
++                            struct in6_addr *home,
++                            struct in6_addr *coa,
++                            struct in6_addr *cn,
++                            struct in6_addr *unused,
++                            struct mipv6_mh *mh)
++{
++      struct mipv6_mh_be *be = (struct mipv6_mh_be *)mh->data;
++      int msg_len = (mh->length+1) << 3;
++      int opt_len;
++      struct in6_addr *hoa;
++      struct bul_inval_args args;
++
++      DEBUG_FUNC();
++
++      if (msg_len > skb->len)
++              return -1;
++
++      opt_len = msg_len - sizeof(*mh) - sizeof(*be);
++
++      if (opt_len < 0) {
++              __u32 pos = (__u32)&mh->length - (__u32)skb->nh.raw;
++              icmpv6_send(skb, ICMPV6_PARAMPROB,
++                          ICMPV6_HDR_FIELD, pos, skb->dev);
++
++              DEBUG(DBG_WARNING, "Mobility Header length less than BE");
++              MIPV6_INC_STATS(n_be_drop.invalid);
++              return -1;
++      }
++
++      
++      if (!ipv6_addr_any(&be->home_addr))
++              hoa = &be->home_addr;
++      else
++              hoa = home;
++
++      MIPV6_INC_STATS(n_be_rcvd);
++
++      args.all_rr_states = 0;
++      args.cn = cn;
++      args.mn = hoa;
++
++      switch (be->status) {
++      case 1: /* Home Address Option used without a binding */
++              /* Get ULP information about CN-MN communication.  If
++                   nothing in progress, MUST delete.  Otherwise MAY
++                   ignore. */
++              args.all_rr_states = 1;
++      case 2: /* Received unknown MH type */
++              /* If not expecting ack, SHOULD ignore.  If MH
++                   extension in use, stop it.  If not, stop RO for
++                   this CN. */
++              write_lock(&bul_lock);
++              mipv6_bul_iterate(mn_bul_invalidate, &args);
++              write_unlock(&bul_lock);
++              break;
++      }
++
++      return 0;
++}
++
++/*
++ * mipv6_bu_rate_limit() : Takes a bulentry, a COA and 'flags' to check
++ * whether BU being sent is for Home Registration or not.
++ *
++ * If the number of BU's sent is fewer than MAX_FAST_UPDATES, this BU
++ * is allowed to be sent at the MAX_UPDATE_RATE.
++ * If the number of BU's sent is greater than or equal to MAX_FAST_UPDATES,
++ * this BU is allowed to be sent at the SLOW_UPDATE_RATE.
++ *
++ * Assumption : This function is not re-entrant. and the caller holds the
++ * bulentry lock (by calling mipv6_bul_get()) to stop races with other
++ * CPU's executing this same function.
++ *
++ * Side-Effects. Either of the following could  on success :
++ *    1. Sets consecutive_sends to 1 if the entry is a Home agent
++ *       registration or the COA has changed.
++ *    2. Increments consecutive_sends if the number of BU's sent so
++ *       far is less than MAX_FAST_UPDATES, and this BU is being sent
++ *       atleast MAX_UPDATE_RATE after previous one.
++ * 
++ * Return Value : 0 on Success, -1 on Failure
++ */
++static int mipv6_bu_rate_limit(struct mipv6_bul_entry *bulentry, 
++                             struct in6_addr *coa, __u8 flags)
++{
++      if ((flags & MIPV6_BU_F_HOME) || ipv6_addr_cmp(&bulentry->coa, coa)) {
++              /* Home Agent Registration or different COA - restart from 1 */
++              bulentry->consecutive_sends = 1;
++              return 0;
++      }
++
++      if (bulentry->consecutive_sends < MAX_FAST_UPDATES) {
++              /* First MAX_FAST_UPDATES can be sent at MAX_UPDATE_RATE */
++              if (jiffies - bulentry->lastsend < MAX_UPDATE_RATE * HZ) {
++                      return -1;
++              }
++              bulentry->consecutive_sends ++;
++      } else {
++              /* Remaining updates SHOULD be sent at SLOW_UPDATE_RATE */
++              if (jiffies - bulentry->lastsend < SLOW_UPDATE_RATE * HZ) {
++                      return -1;
++              }
++              /* Don't inc 'consecutive_sends' to avoid overflow to zero */
++      }
++      /* OK to send a BU */
++      return 0;
++}
++
++/**
++ * mipv6_send_bu - send a Binding Update 
++ * @saddr: source address for BU
++ * @daddr: destination address for BU
++ * @coa: care-of address for MN
++ * @initdelay: initial BA wait timeout
++ * @maxackdelay: maximum BA wait timeout
++ * @exp: exponention back off
++ * @flags: flags for BU
++ * @lifetime: granted lifetime for binding
++ * @ops: mobility options
++ *
++ * Send a binding update.  'flags' may contain any of %MIPV6_BU_F_ACK,
++ * %MIPV6_BU_F_HOME, %MIPV6_BU_F_ROUTER bitwise ORed.  If
++ * %MIPV6_BU_F_ACK is included retransmission will be attempted until
++ * the update has been acknowledged.  Retransmission is done if no
++ * acknowledgement is received within @initdelay seconds.  @exp
++ * specifies whether to use exponential backoff (@exp != 0) or linear
++ * backoff (@exp == 0).  For exponential backoff the time to wait for
++ * an acknowledgement is doubled on each retransmission until a delay
++ * of @maxackdelay, after which retransmission is no longer attempted.
++ * For linear backoff the delay is kept constant and @maxackdelay
++ * specifies the maximum number of retransmissions instead.  If
++ * sub-options are present ops must contain all sub-options to be
++ * added.  On a mobile node, use the mobile node's home address for
++ * @saddr.  Returns 0 on success, non-zero on failure.
++ *
++ * Caller may not hold @bul_lock.
++ **/
++int mipv6_send_bu(struct in6_addr *saddr, struct in6_addr *daddr,
++                struct in6_addr *coa, u32 initdelay, 
++                u32 maxackdelay, u8 exp, u8 flags, u32 lifetime,
++                struct mipv6_mh_opt *ops)
++{
++      int ret;
++      __u8 state;
++       __u16 seq = 0;
++      int (*callback)(struct mipv6_bul_entry *);
++      __u32 callback_time;
++      struct mipv6_bul_entry *bulentry;
++      
++      /* First a sanity check: don't send BU to local addresses */
++      if(ipv6_chk_addr(daddr, NULL)) {
++              DEBUG(DBG_ERROR, "BUG: Trying to send BU to local address");
++              return -1;
++      }
++      DEBUG(DBG_INFO, "Sending BU to CN  %x:%x:%x:%x:%x:%x:%x:%x "
++            "for home address %x:%x:%x:%x:%x:%x:%x:%x", 
++            NIPV6ADDR(daddr), NIPV6ADDR(saddr));
++
++      if ((bulentry = mipv6_bul_get(daddr, saddr)) != NULL) {
++              if (bulentry->state == ACK_ERROR) {
++                      /*
++                       * Don't send any more BU's to nodes which don't
++                       * understanding one. 
++                       */
++                      DEBUG(DBG_INFO, "Not sending BU to node which doesn't"
++                            " understand one");
++                      return -1;
++              }
++              if (mipv6_bu_rate_limit(bulentry, coa, flags) < 0) {
++                      DEBUG(DBG_DATADUMP, "Limiting BU sent.");
++                      return 0;
++              }
++      }
++
++      switch (mipv6_rr_state(bulentry, saddr, coa, flags)) {
++      case INPROGRESS_RR:
++              /* We are already doing RR, don't do BU at this time, it is
++               * done automatically later */
++              DEBUG(DBG_INFO, "RR in progress not sending BU");
++              return 0;
++
++      case DO_RR:
++              /* Just do RR and return, BU is done automatically later */
++              DEBUG(DBG_INFO, "starting RR" );
++              mipv6_RR_start(saddr, daddr, coa, bulentry, initdelay,
++                             maxackdelay, flags, lifetime, ops);
++              return 0;
++              
++      case NO_RR:
++              DEBUG(DBG_DATADUMP, "No RR necessary" );
++      default:
++              break;
++      }
++
++      if (bulentry)
++              seq = bulentry->seq + 1;
++      
++      /* Add to binding update list */
++      
++      if (flags & MIPV6_BU_F_ACK) {
++              DEBUG(DBG_INFO, "Setting bul callback to bul_resend_exp");
++              /* Send using exponential backoff */
++              state = RESEND_EXP;
++              callback = bul_resend_exp;
++              callback_time = initdelay;
++      } else {
++              DEBUG(DBG_INFO, "Setting bul callback to bul_entry_expired");
++              /* No acknowledgement/resending required */
++              state = ACK_OK; /* pretend we got an ack */
++              callback = bul_entry_expired;
++              callback_time = lifetime;
++      }
++
++      /* BU only for the home address */
++      /* We must hold bul_lock (write) while calling add */
++      if ((bulentry = mipv6_bul_add(daddr, saddr, coa, lifetime, seq,
++                                    flags, callback, callback_time, 
++                                    state, initdelay, maxackdelay, ops, 
++                                    NULL)) == NULL) {
++              DEBUG(DBG_INFO, "couldn't update BUL");
++              return 0;
++      }
++      ret = send_bu_msg(bulentry);
++
++      return ret;
++}
++
++int __init mipv6_mh_mn_init(void)
++{
++      mipv6_mh_register(MIPV6_MH_HOT, mipv6_handle_mh_HC_test);
++      mipv6_mh_register(MIPV6_MH_COT, mipv6_handle_mh_HC_test);
++      mipv6_mh_register(MIPV6_MH_BA, mipv6_handle_mh_ba);
++      mipv6_mh_register(MIPV6_MH_BRR, mipv6_handle_mh_brr);
++      mipv6_mh_register(MIPV6_MH_BE, mipv6_handle_mh_be);
++
++      return 0;
++}
++
++void __exit mipv6_mh_mn_exit(void)
++{
++      mipv6_mh_unregister(MIPV6_MH_HOT);
++      mipv6_mh_unregister(MIPV6_MH_COT);
++      mipv6_mh_unregister(MIPV6_MH_BA);
++      mipv6_mh_unregister(MIPV6_MH_BRR);
++      mipv6_mh_unregister(MIPV6_MH_BE);
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/module_cn.c linux-2.4.25/net/ipv6/mobile_ip6/module_cn.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/module_cn.c   1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/module_cn.c       2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,167 @@
++/*
++ *    Mobile IPv6 Common Module
++ *
++ *    Authors:
++ *    Sami Kivisaari          <skivisaa@cc.hut.fi>
++ *    Antti Tuominen          <ajtuomin@tml.hut.fi>
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ */
++
++#include <linux/config.h>
++#include <linux/module.h>
++#include <linux/init.h>
++
++#ifdef CONFIG_SYSCTL
++#include <linux/sysctl.h>
++#endif /* CONFIG_SYSCTL */
++
++#include <net/mipglue.h>
++
++#include "bcache.h"
++#include "mipv6_icmp.h"
++#include "stats.h"
++#include "mobhdr.h"
++#include "exthdrs.h"
++
++int mipv6_debug = 1;
++
++#if defined(MODULE) && LINUX_VERSION_CODE > 0x20115
++MODULE_AUTHOR("MIPL Team");
++MODULE_DESCRIPTION("Mobile IPv6");
++MODULE_LICENSE("GPL");
++MODULE_PARM(mipv6_debug, "i");
++#endif
++
++#include "config.h"
++
++struct mip6_func mip6_fn;
++struct mip6_conf mip6node_cnf = {
++      capabilities:           CAP_CN,
++      accept_ret_rout:        1,
++      max_rtr_reachable_time: 0,
++      eager_cell_switching:   0,
++      max_num_tunnels:        0,
++      min_num_tunnels:        0,
++      binding_refresh_advice: 0,
++      bu_lladdr:              0,
++      bu_keymgm:              0,
++      bu_cn_ack:              0
++};
++
++#define MIPV6_BCACHE_SIZE 128
++
++/**********************************************************************
++ *
++ * MIPv6 CN Module Init / Cleanup
++ *
++ **********************************************************************/
++
++#ifdef CONFIG_SYSCTL
++/* Sysctl table */
++ctl_table mipv6_mobility_table[] = {
++      {NET_IPV6_MOBILITY_DEBUG, "debuglevel",
++       &mipv6_debug, sizeof(int), 0644, NULL,
++       &proc_dointvec},
++      {NET_IPV6_MOBILITY_RETROUT, "accept_return_routability",
++       &mip6node_cnf.accept_ret_rout, sizeof(int), 0644, NULL,
++       &proc_dointvec},
++      {0}
++};
++ctl_table mipv6_table[] = {
++      {NET_IPV6_MOBILITY, "mobility", NULL, 0, 0555, mipv6_mobility_table},
++      {0}
++};
++
++static struct ctl_table_header *mipv6_sysctl_header;
++static struct ctl_table mipv6_net_table[];
++static struct ctl_table mipv6_root_table[];
++
++ctl_table mipv6_net_table[] = {
++      {NET_IPV6, "ipv6", NULL, 0, 0555, mipv6_table},
++      {0}
++};
++
++ctl_table mipv6_root_table[] = {
++      {CTL_NET, "net", NULL, 0, 0555, mipv6_net_table},
++      {0}
++};
++#endif /* CONFIG_SYSCTL */
++
++extern void mipv6_rr_init(void);
++
++/*  Initialize the module  */
++static int __init mip6_init(void)
++{
++      int err = 0;
++
++      printk(KERN_INFO "MIPL Mobile IPv6 for Linux Correspondent Node %s (%s)\n",
++             MIPLVERSION, MIPV6VERSION);
++
++#ifdef CONFIG_IPV6_MOBILITY_DEBUG
++      printk(KERN_INFO "Debug-level: %d\n", mipv6_debug);
++#endif
++
++      if ((err = mipv6_bcache_init(MIPV6_BCACHE_SIZE)) < 0)
++              goto bcache_fail;
++
++      if ((err = mipv6_icmpv6_init()) < 0)
++              goto icmp_fail;
++
++      if ((err = mipv6_stats_init()) < 0)
++              goto stats_fail;
++      mipv6_rr_init();
++
++#ifdef CONFIG_SYSCTL
++      mipv6_sysctl_header = register_sysctl_table(mipv6_root_table, 0);
++#endif
++
++      if ((err = mipv6_mh_common_init()) < 0)
++              goto mh_fail;
++
++      MIPV6_SETCALL(mipv6_modify_txoptions, mipv6_modify_txoptions);
++              
++      MIPV6_SETCALL(mipv6_handle_homeaddr, mipv6_handle_homeaddr);
++      MIPV6_SETCALL(mipv6_icmp_swap_addrs, mipv6_icmp_swap_addrs);
++
++      return 0;
++
++mh_fail:
++#ifdef CONFIG_SYSCTL
++      unregister_sysctl_table(mipv6_sysctl_header);
++#endif
++      mipv6_stats_exit();
++stats_fail:
++      mipv6_icmpv6_exit();
++icmp_fail:
++      mipv6_bcache_exit();
++bcache_fail:
++      return err;
++}
++module_init(mip6_init);
++
++#ifdef MODULE
++/*  Cleanup module  */
++static void __exit mip6_exit(void)
++{
++      printk(KERN_INFO "mip6_base.o exiting.\n");
++#ifdef CONFIG_SYSCTL
++      unregister_sysctl_table(mipv6_sysctl_header);
++#endif
++
++      /* Invalidate all custom kernel hooks.  No need to do this
++           separately for all hooks. */
++      mipv6_invalidate_calls();
++
++      mipv6_mh_common_exit();
++      mipv6_stats_exit();
++      mipv6_icmpv6_exit();
++      mipv6_bcache_exit();
++}
++module_exit(mip6_exit);
++#endif /* MODULE */
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/module_ha.c linux-2.4.25/net/ipv6/mobile_ip6/module_ha.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/module_ha.c   1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/module_ha.c       2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,264 @@
++/*
++ *    Mobile IPv6 Home Agent Module
++ *
++ *    Authors:
++ *    Sami Kivisaari          <skivisaa@cc.hut.fi>
++ *    Antti Tuominen          <ajtuomin@tml.hut.fi>
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ */
++
++#include <linux/config.h>
++#include <linux/module.h>
++#include <linux/init.h>
++
++#ifdef CONFIG_SYSCTL
++#include <linux/sysctl.h>
++#endif /* CONFIG_SYSCTL */
++
++#include <net/mipglue.h>
++#include <net/addrconf.h>
++
++#include "mobhdr.h"
++#include "tunnel_ha.h"
++#include "ha.h"
++#include "halist.h"
++#include "mipv6_icmp.h"
++//#include "prefix.h"
++#include "bcache.h"
++#include "debug.h"
++
++int mipv6_use_auth = 0;
++
++#if defined(MODULE) && LINUX_VERSION_CODE > 0x20115
++MODULE_AUTHOR("MIPL Team");
++MODULE_DESCRIPTION("Mobile IPv6 Home Agent");
++MODULE_LICENSE("GPL");
++#endif
++
++#include "config.h"
++
++#define MIPV6_HALIST_SIZE 128
++struct ha_info_opt {
++      u8 type;
++      u8 len;
++      u16 res;
++      u16 pref;
++      u16 ltime;
++};
++/*
++ * Called from ndisc.c's router_discovery.
++ */
++static int mipv6_ha_ra_rcv(struct sk_buff *skb, struct ndisc_options *ndopts)
++{
++      unsigned int ha_info_pref = 0, ha_info_lifetime;
++      int ifi = ((struct inet6_skb_parm *)skb->cb)->iif;
++      struct ra_msg *ra = (struct ra_msg *) skb->h.raw;
++      struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
++      struct in6_addr ll_addr;
++      struct hal {
++              struct in6_addr prefix;
++              int plen;
++              struct hal *next;
++      };
++      
++      DEBUG_FUNC();
++
++      ha_info_lifetime = ntohs(ra->icmph.icmp6_rt_lifetime);
++      ipv6_addr_copy(&ll_addr, saddr);
++      
++      if (ndopts->nd_opts_hai) {
++              struct ha_info_opt *hai = (struct ha_info_opt *)ndopts->nd_opts_hai;
++              ha_info_pref = ntohs(hai->pref);
++              ha_info_lifetime = ntohs(hai->ltime);
++              DEBUG(DBG_DATADUMP,
++                    "received home agent info with preference : %d and lifetime : %d",
++                    ha_info_pref, ha_info_lifetime);
++      }
++      if (ndopts->nd_opts_pi) {
++              struct nd_opt_hdr *p;
++              for (p = ndopts->nd_opts_pi;
++                   p;
++                   p = ndisc_next_option(p, ndopts->nd_opts_pi_end)) {
++                      struct prefix_info *pinfo;
++                      
++                      pinfo = (struct prefix_info *) p;
++                      
++                      if (pinfo->router_address) {
++                              DEBUG(DBG_DATADUMP, "Adding router address to "
++                                    "ha queue \n");
++                              /* If RA has H bit set and Prefix Info
++                               * Option R bit set, queue this
++                               * address to be added to Home Agents
++                               * List.  
++                               */
++                              if (ipv6_addr_type(&pinfo->prefix) &
++                                  IPV6_ADDR_LINKLOCAL)
++                                      continue;
++                              if (!ra->icmph.icmp6_home_agent || !ha_info_lifetime) {
++                                      mipv6_halist_delete(&pinfo->prefix); 
++                                      continue;
++                              } else {
++                                      
++                                      mipv6_halist_add(ifi, &pinfo->prefix, 
++                                                       pinfo->prefix_len, &ll_addr, 
++                                                       ha_info_pref, ha_info_lifetime);
++                              } 
++                              
++                      }
++                      
++              }
++      }
++      return MIPV6_ADD_RTR;
++}
++
++/**********************************************************************
++ *
++ * MIPv6 Module Init / Cleanup
++ *
++ **********************************************************************/
++
++#ifdef CONFIG_SYSCTL
++/* Sysctl table */
++extern int 
++mipv6_max_tnls_sysctl(ctl_table *, int, struct file *, void *, size_t *);
++
++extern int 
++mipv6_min_tnls_sysctl(ctl_table *, int, struct file *, void *, size_t *);
++
++int max_adv = ~(u16)0;
++int min_zero = 0;
++ctl_table mipv6_mobility_table[] = {
++      {NET_IPV6_MOBILITY_BINDING_REFRESH, "binding_refresh_advice",
++       &mip6node_cnf.binding_refresh_advice, sizeof(int), 0644, NULL,
++       &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_adv},
++
++      {NET_IPV6_MOBILITY_MAX_TNLS, "max_tnls", &mipv6_max_tnls, sizeof(int),
++       0644, NULL, &mipv6_max_tnls_sysctl},
++      {NET_IPV6_MOBILITY_MIN_TNLS, "min_tnls", &mipv6_min_tnls, sizeof(int),
++       0644, NULL, &mipv6_min_tnls_sysctl},
++      {0}
++};
++ctl_table mipv6_table[] = {
++      {NET_IPV6_MOBILITY, "mobility", NULL, 0, 0555, mipv6_mobility_table},
++      {0}
++};
++
++static struct ctl_table_header *mipv6_sysctl_header;
++static struct ctl_table mipv6_net_table[];
++static struct ctl_table mipv6_root_table[];
++
++ctl_table mipv6_net_table[] = {
++      {NET_IPV6, "ipv6", NULL, 0, 0555, mipv6_table},
++      {0}
++};
++
++ctl_table mipv6_root_table[] = {
++      {CTL_NET, "net", NULL, 0, 0555, mipv6_net_table},
++      {0}
++};
++#endif /* CONFIG_SYSCTL */
++
++extern void mipv6_check_dad(struct in6_addr *haddr);
++extern void mipv6_dad_init(void);
++extern void mipv6_dad_exit(void);
++extern int mipv6_forward(struct sk_buff *);
++
++/*  Initialize the module  */
++static int __init mip6_ha_init(void)
++{
++      int err = 0;
++
++      printk(KERN_INFO "MIPL Mobile IPv6 for Linux Home Agent %s (%s)\n",
++             MIPLVERSION, MIPV6VERSION);
++      mip6node_cnf.capabilities = CAP_CN | CAP_HA;
++
++      mip6_fn.icmpv6_dhaad_rep_rcv = mipv6_icmpv6_no_rcv;
++      mip6_fn.icmpv6_dhaad_req_rcv = mipv6_icmpv6_rcv_dhaad_req;
++      mip6_fn.icmpv6_pfxadv_rcv = mipv6_icmpv6_no_rcv;
++      mip6_fn.icmpv6_pfxsol_rcv = mipv6_icmpv6_no_rcv;
++      mip6_fn.icmpv6_paramprob_rcv = mipv6_icmpv6_no_rcv;
++
++#ifdef CONFIG_IPV6_MOBILITY_DEBUG
++      printk(KERN_INFO "Debug-level: %d\n", mipv6_debug);
++#endif
++
++#ifdef CONFIG_SYSCTL
++      mipv6_sysctl_header = register_sysctl_table(mipv6_root_table, 0);
++#endif
++      mipv6_initialize_tunnel();
++
++      if ((err = mipv6_ha_init()) < 0)
++              goto ha_fail;
++
++      MIPV6_SETCALL(mipv6_ra_rcv, mipv6_ha_ra_rcv);
++      MIPV6_SETCALL(mipv6_forward, mipv6_forward);
++      mipv6_dad_init();
++      MIPV6_SETCALL(mipv6_check_dad, mipv6_check_dad);
++
++      if ((err = mipv6_halist_init(MIPV6_HALIST_SIZE)) < 0)
++              goto halist_fail;
++
++//    mipv6_initialize_pfx_icmpv6();
++
++      return 0;
++
++halist_fail:
++      mipv6_dad_exit();
++      mipv6_ha_exit();
++ha_fail:
++      mipv6_shutdown_tunnel();
++
++      mip6_fn.icmpv6_dhaad_rep_rcv = NULL;
++      mip6_fn.icmpv6_dhaad_req_rcv = NULL;
++      mip6_fn.icmpv6_pfxadv_rcv = NULL;
++      mip6_fn.icmpv6_pfxsol_rcv = NULL;
++      mip6_fn.icmpv6_paramprob_rcv = NULL;
++
++      MIPV6_RESETCALL(mipv6_ra_rcv);
++      MIPV6_RESETCALL(mipv6_forward);
++      MIPV6_RESETCALL(mipv6_check_dad);
++
++#ifdef CONFIG_SYSCTL
++      unregister_sysctl_table(mipv6_sysctl_header);
++#endif
++      return err;
++}
++module_init(mip6_ha_init);
++
++#ifdef MODULE
++/*  Cleanup module  */
++static void __exit mip6_ha_exit(void)
++{
++      printk(KERN_INFO "mip6_ha.o exiting.\n");
++      mip6node_cnf.capabilities &= ~(int)CAP_HA;
++
++      mipv6_bcache_cleanup(HOME_REGISTRATION);
++
++      MIPV6_RESETCALL(mipv6_ra_rcv);
++      MIPV6_RESETCALL(mipv6_forward);
++      MIPV6_RESETCALL(mipv6_check_dad);
++
++      mipv6_halist_exit();
++//    mipv6_shutdown_pfx_icmpv6();
++
++      mip6_fn.icmpv6_dhaad_rep_rcv = NULL;
++      mip6_fn.icmpv6_dhaad_req_rcv = NULL;
++      mip6_fn.icmpv6_pfxadv_rcv = NULL;
++      mip6_fn.icmpv6_pfxsol_rcv = NULL;
++      mip6_fn.icmpv6_paramprob_rcv = NULL;
++
++      mipv6_dad_exit();
++      mipv6_ha_exit();
++      mipv6_shutdown_tunnel();
++#ifdef CONFIG_SYSCTL
++      unregister_sysctl_table(mipv6_sysctl_header);
++#endif
++}
++module_exit(mip6_ha_exit);
++#endif /* MODULE */
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/module_mn.c linux-2.4.25/net/ipv6/mobile_ip6/module_mn.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/module_mn.c   1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/module_mn.c       2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,188 @@
++/*
++ *    Mobile IPv6 Mobile Node Module
++ *
++ *    Authors:
++ *    Sami Kivisaari          <skivisaa@cc.hut.fi>
++ *    Antti Tuominen          <ajtuomin@tml.hut.fi>
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ */
++
++#include <linux/config.h>
++#include <linux/module.h>
++#include <linux/init.h>
++
++#ifdef CONFIG_SYSCTL
++#include <linux/sysctl.h>
++#endif /* CONFIG_SYSCTL */
++
++#include <net/mipglue.h>
++
++extern int mipv6_debug;
++int mipv6_use_auth = 0;
++
++#if defined(MODULE) && LINUX_VERSION_CODE > 0x20115
++MODULE_AUTHOR("MIPL Team");
++MODULE_DESCRIPTION("Mobile IPv6 Mobile Node");
++MODULE_LICENSE("GPL");
++MODULE_PARM(mipv6_debug, "i");
++#endif
++
++#include "config.h"
++
++#include "mobhdr.h"
++#include "mn.h"
++#include "mipv6_icmp.h"
++//#include "prefix.h"
++
++/* TODO: These will go as soon as we get rid of the last two ioctls */
++extern int mipv6_ioctl_mn_init(void);
++extern void mipv6_ioctl_mn_exit(void);
++
++/**********************************************************************
++ *
++ * MIPv6 Module Init / Cleanup
++ *
++ **********************************************************************/
++
++#ifdef CONFIG_SYSCTL
++/* Sysctl table */
++
++extern int max_rtr_reach_time;
++extern int eager_cell_switching;
++
++static int max_reach = 1000;
++static int min_reach = 1;
++static int max_one = 1;
++static int min_zero = 0;
++
++extern int 
++mipv6_mdetect_mech_sysctl(ctl_table *, int, struct file *, void *, size_t *);
++
++extern int 
++mipv6_router_reach_sysctl(ctl_table *, int, struct file *, void *, size_t *);
++
++ctl_table mipv6_mobility_table[] = {
++      {NET_IPV6_MOBILITY_BU_F_LLADDR, "bu_flag_lladdr",
++       &mip6node_cnf.bu_lladdr, sizeof(int), 0644, NULL,
++       &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_one},
++      {NET_IPV6_MOBILITY_BU_F_KEYMGM, "bu_flag_keymgm",
++       &mip6node_cnf.bu_keymgm, sizeof(int), 0644, NULL,
++       &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_one},
++      {NET_IPV6_MOBILITY_BU_F_CN_ACK, "bu_flag_cn_ack",
++       &mip6node_cnf.bu_cn_ack, sizeof(int), 0644, NULL,
++       &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_one},
++
++      {NET_IPV6_MOBILITY_ROUTER_REACH, "max_router_reachable_time",
++       &max_rtr_reach_time, sizeof(int), 0644, NULL,
++       &proc_dointvec_minmax, &sysctl_intvec, 0, &min_reach, &max_reach},
++
++      {NET_IPV6_MOBILITY_MDETECT_MECHANISM, "eager_cell_switching",
++       &eager_cell_switching, sizeof(int), 0644, NULL,
++       &proc_dointvec_minmax, &sysctl_intvec, 0, &min_zero, &max_one},
++
++      {0}
++};
++ctl_table mipv6_table[] = {
++      {NET_IPV6_MOBILITY, "mobility", NULL, 0, 0555, mipv6_mobility_table},
++      {0}
++};
++
++static struct ctl_table_header *mipv6_sysctl_header;
++static struct ctl_table mipv6_net_table[];
++static struct ctl_table mipv6_root_table[];
++
++ctl_table mipv6_net_table[] = {
++      {NET_IPV6, "ipv6", NULL, 0, 0555, mipv6_table},
++      {0}
++};
++
++ctl_table mipv6_root_table[] = {
++      {CTL_NET, "net", NULL, 0, 0555, mipv6_net_table},
++      {0}
++};
++#endif /* CONFIG_SYSCTL */
++
++/*  Initialize the module  */
++static int __init mip6_mn_init(void)
++{
++      int err = 0;
++
++      printk(KERN_INFO "MIPL Mobile IPv6 for Linux Mobile Node %s (%s)\n",
++             MIPLVERSION, MIPV6VERSION);
++      mip6node_cnf.capabilities = CAP_CN | CAP_MN;
++
++#ifdef CONFIG_IPV6_MOBILITY_DEBUG
++      printk(KERN_INFO "Debug-level: %d\n", mipv6_debug);
++#endif
++
++#ifdef CONFIG_SYSCTL
++      mipv6_sysctl_header = register_sysctl_table(mipv6_root_table, 0);
++#endif
++      if ((err = mipv6_mn_init()) < 0)
++              goto mn_fail;
++
++      mipv6_mh_mn_init();
++
++      mip6_fn.icmpv6_dhaad_rep_rcv = mipv6_icmpv6_rcv_dhaad_rep;
++      mip6_fn.icmpv6_dhaad_req_rcv = mipv6_icmpv6_no_rcv;
++      mip6_fn.icmpv6_pfxadv_rcv = mipv6_icmpv6_no_rcv;
++      mip6_fn.icmpv6_pfxsol_rcv = mipv6_icmpv6_no_rcv;
++      mip6_fn.icmpv6_paramprob_rcv = mipv6_icmpv6_rcv_paramprob;
++
++//    mipv6_initialize_pfx_icmpv6();
++
++      if ((err = mipv6_ioctl_mn_init()) < 0)
++              goto ioctl_fail;
++
++      return 0;
++
++ioctl_fail:
++//    mipv6_shutdown_pfx_icmpv6();
++
++      mip6_fn.icmpv6_dhaad_rep_rcv = NULL;
++      mip6_fn.icmpv6_dhaad_req_rcv = NULL;
++      mip6_fn.icmpv6_pfxadv_rcv = NULL;
++      mip6_fn.icmpv6_pfxsol_rcv = NULL;
++      mip6_fn.icmpv6_paramprob_rcv = NULL;
++
++      mipv6_mh_mn_exit();
++      mipv6_mn_exit();
++mn_fail:
++#ifdef CONFIG_SYSCTL
++      unregister_sysctl_table(mipv6_sysctl_header);
++#endif
++      return err;
++}
++module_init(mip6_mn_init);
++
++#ifdef MODULE
++/*  Cleanup module  */
++static void __exit mip6_mn_exit(void)
++{
++      printk(KERN_INFO "mip6_mn.o exiting.\n");
++      mip6node_cnf.capabilities &= ~(int)CAP_MN;
++
++      mipv6_ioctl_mn_exit();
++//    mipv6_shutdown_pfx_icmpv6();
++
++      mip6_fn.icmpv6_dhaad_rep_rcv = NULL;
++      mip6_fn.icmpv6_dhaad_req_rcv = NULL;
++      mip6_fn.icmpv6_pfxadv_rcv = NULL;
++      mip6_fn.icmpv6_pfxsol_rcv = NULL;
++      mip6_fn.icmpv6_paramprob_rcv = NULL;
++
++      mipv6_mn_exit();
++
++/* common cleanup */
++#ifdef CONFIG_SYSCTL
++      unregister_sysctl_table(mipv6_sysctl_header);
++#endif
++}
++module_exit(mip6_mn_exit);
++#endif /* MODULE */
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/multiaccess_ctl.c linux-2.4.25/net/ipv6/mobile_ip6/multiaccess_ctl.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/multiaccess_ctl.c     1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/multiaccess_ctl.c 2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,287 @@
++/*  
++ * 2001 (c) Oy L M Ericsson Ab
++ *
++ * Author: NomadicLab / Ericsson Research <ipv6@nomadiclab.com>
++ *
++ * $Id$
++ *
++ */
++
++/*
++ * Vertical hand-off information manager
++ */
++
++#include <linux/netdevice.h>
++#include <linux/in6.h>
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/proc_fs.h>
++#include <linux/string.h>
++#include <linux/kernel.h>
++#include <asm/io.h>
++#include <asm/uaccess.h>
++#include <linux/list.h>
++#include "multiaccess_ctl.h"
++#include "debug.h"
++
++/*
++ * Local variables
++ */
++static LIST_HEAD(if_list);
++
++/* Internal interface information list */
++struct ma_if_info {
++      struct list_head list;
++      int        interface_id;
++      int        preference;
++      __u8       status;
++};
++
++/**
++ * ma_ctl_get_preference - get preference value for interface
++ * @ifi: interface index
++ * 
++ * Returns integer value preference for given interface.
++ **/
++int ma_ctl_get_preference(int ifi)
++{
++      struct list_head *lh;
++      struct ma_if_info *info;
++      int pref = 0;
++
++      list_for_each(lh, &if_list) {
++              info = list_entry(lh, struct ma_if_info, list);
++              if (info->interface_id == ifi) {
++                      pref = info->preference;
++                      return pref;
++              }
++      }
++      return -1;
++}
++/**
++ * ma_ctl_get_preference - get preference value for interface
++ * @ifi: interface index
++ * 
++ * Returns integer value interface index for interface with highest preference.
++ **/
++int ma_ctl_get_preferred_if(void)
++{
++      struct list_head *lh;
++      struct ma_if_info *info, *pref_if = NULL;
++      
++      list_for_each(lh, &if_list) {
++              info = list_entry(lh, struct ma_if_info, list);
++              if (!pref_if || (info->preference > pref_if->preference)) {
++                      pref_if = info;
++              }
++      }
++      if (pref_if) return pref_if->interface_id;
++      return 0;
++}
++/**
++ * ma_ctl_set_preference - set preference for interface
++ * @arg: ioctl args
++ *
++ * Sets preference of an existing interface (called by ioctl).
++ **/
++void ma_ctl_set_preference(unsigned long arg)
++{
++      struct list_head *lh;
++      struct ma_if_info *info;
++      struct ma_if_uinfo uinfo;
++      
++      memset(&uinfo, 0, sizeof(struct ma_if_uinfo));
++      if (copy_from_user(&uinfo, (struct ma_if_uinfo *)arg, 
++                         sizeof(struct ma_if_uinfo)) < 0) {
++              DEBUG(DBG_WARNING, "copy_from_user failed");
++              return;
++      }
++
++      /* check if the interface exists */
++      list_for_each(lh, &if_list) {
++              info = list_entry(lh, struct ma_if_info, list);
++              if (info->interface_id == uinfo.interface_id) {
++                      info->preference = uinfo.preference;
++                      return;
++              }
++      }
++}
++
++/**
++ * ma_ctl_add_iface - add new interface to list
++ * @if_index: interface index
++ *
++ * Adds new interface entry to preference list.  Preference is set to
++ * the same value as @if_index.  Entry @status is set to
++ * %MA_IFACE_NOT_USED.
++ **/
++void ma_ctl_add_iface(int if_index)
++{
++      struct list_head *lh;
++      struct ma_if_info *info;
++
++      DEBUG_FUNC();
++      
++      /* check if the interface already exists */
++      list_for_each(lh, &if_list) {
++              info = list_entry(lh, struct ma_if_info, list);
++              if (info->interface_id == if_index) {
++                      info->status = MA_IFACE_NOT_USED;
++                      info->preference = if_index;
++                      return;
++              }
++      }
++
++      info = kmalloc(sizeof(struct ma_if_info), GFP_ATOMIC);
++      if (info == NULL) {
++              DEBUG(DBG_ERROR, "Out of memory");
++              return;
++      }
++      memset(info, 0, sizeof(struct ma_if_info));
++      info->interface_id = if_index;
++      info->preference = if_index;
++      info->status = MA_IFACE_NOT_USED;
++      list_add(&info->list, &if_list);
++}
++
++/**
++ * ma_ctl_del_iface - remove entry from the list
++ * @if_index: interface index
++ *
++ * Removes entry for interface @if_index from preference list.
++ **/
++int ma_ctl_del_iface(int if_index)
++{
++      struct list_head *lh, *next;
++      struct ma_if_info *info;
++
++      DEBUG_FUNC();
++
++      /* if the iface exists, change availability to 0 */
++      list_for_each_safe(lh, next, &if_list) {
++              info = list_entry(lh, struct ma_if_info, list);
++              if (info->interface_id == if_index) {
++                      list_del(&info->list);
++                      kfree(info);
++                      return 0;
++              }
++      }
++
++      return -1;
++}
++
++/**
++ * ma_ctl_upd_iface - update entry (and list)
++ * @if_index: interface to update
++ * @status: new status for interface
++ * @change_if_index: new interface
++ *
++ * Updates @if_index entry on preference list.  Entry status is set to
++ * @status.  If new @status is %MA_IFACE_CURRENT, updates list to have
++ * only one current device.  If @status is %MA_IFACE_NOT_PRESENT,
++ * entry is deleted and further if entry had %MA_IFACE_CURRENT set,
++ * new current device is looked up and returned in @change_if_index.
++ * New preferred interface is also returned if current device changes
++ * to %MA_IFACE_NOT_USED.  Returns 0 on success, otherwise negative.
++ **/
++int ma_ctl_upd_iface(int if_index, int status, int *change_if_index)
++{
++      struct list_head *lh, *tmp;
++      struct ma_if_info *info, *pref = NULL;
++      int found = 0;
++
++      DEBUG_FUNC();
++
++      *change_if_index = 0;
++
++      /* check if the interface exists */
++      list_for_each_safe(lh, tmp, &if_list) {
++              info = list_entry(lh, struct ma_if_info, list);
++              if (status == MA_IFACE_NOT_PRESENT) {
++                      if (info->interface_id == if_index) {
++                              list_del_init(&info->list);
++                              kfree(info);
++                              found = 1;
++                              break;
++                      }
++              } else if (status == MA_IFACE_CURRENT) {
++                      if (info->interface_id == if_index) {
++                              info->status |= MA_IFACE_CURRENT;
++                              found = 1;
++                      } else {
++                              info->status |= MA_IFACE_NOT_USED;
++                      }
++              } else if (status == MA_IFACE_NOT_USED) {
++                      if (info->interface_id == if_index) {
++                              if (info->status | MA_IFACE_CURRENT) {
++                                      found = 1;
++                              }
++                              info->status &= !MA_IFACE_CURRENT;
++                              info->status |= MA_IFACE_NOT_USED;
++                              info->status &= !MA_IFACE_HAS_ROUTER;
++                      }
++                      break;
++              } else if (status == MA_IFACE_HAS_ROUTER) {
++                      if (info->interface_id == if_index) {
++                              info->status |= MA_IFACE_HAS_ROUTER;
++                      }
++                      return 0;
++              }
++      }
++
++      if (status & (MA_IFACE_NOT_USED|MA_IFACE_NOT_PRESENT) && found) {
++              /* select new interface */
++              list_for_each(lh, &if_list) {
++                      info = list_entry(lh, struct ma_if_info, list);
++                      if (pref == NULL || ((info->preference > pref->preference) && 
++                                           info->status & MA_IFACE_HAS_ROUTER))
++                              pref = info;
++              }
++              if (pref) {
++                      *change_if_index = pref->interface_id;
++                      pref->status |= MA_IFACE_CURRENT;
++              } else {
++                      *change_if_index = -1;
++              }
++              return 0;
++      }
++
++      if (found) return 0;
++
++      return -1;
++}
++
++static int if_proc_info(char *buffer, char **start, off_t offset,
++                      int length)
++{
++      struct list_head *lh;
++      struct ma_if_info *info;
++      int len = 0;
++
++      list_for_each(lh, &if_list) {
++              info = list_entry(lh, struct ma_if_info, list);
++              len += sprintf(buffer + len, "%02d %010d %1d %1d\n",
++                             info->interface_id, info->preference,
++                             !!(info->status & MA_IFACE_HAS_ROUTER),
++                             !!(info->status & MA_IFACE_CURRENT));
++      }
++
++      *start = buffer + offset;
++
++      len -= offset;
++
++      if (len > length) len = length;
++
++      return len;
++
++}
++
++void ma_ctl_init(void)
++{
++      proc_net_create("mip6_iface", 0, if_proc_info);
++}
++
++void ma_ctl_clean(void)
++{
++      proc_net_remove("mip6_iface");
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/multiaccess_ctl.h linux-2.4.25/net/ipv6/mobile_ip6/multiaccess_ctl.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/multiaccess_ctl.h     1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/multiaccess_ctl.h 2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,77 @@
++/*  
++ * 2001 (c) Oy L M Ericsson Ab
++ *
++ * Author: NomadicLab / Ericsson Research <ipv6@nomadiclab.com>
++ *
++ * $Id$
++ *
++ */
++
++#ifndef _MULTIACCESS_CTL_H
++#define _MULTIACCESS_CTL_H
++
++/* status */
++#define MA_IFACE_NOT_PRESENT 0x01
++#define MA_IFACE_NOT_USED    0x02
++#define MA_IFACE_HAS_ROUTER  0x04
++#define MA_IFACE_CURRENT     0x10
++
++struct ma_if_uinfo {
++      int        interface_id;
++      int        preference;
++      __u8       status;
++};
++/*
++ *  @ma_ctl_get_preferred_id: returns most preferred interface id
++ */
++int ma_ctl_get_preferred_if(void);
++
++/* @ma_ctl_get_preference: returns preference for an interface
++ * @name: name of the interface (dev->name)
++ */
++int ma_ctl_get_preference(int ifi);
++
++/*
++ * Public function: ma_ctl_set_preference
++ * Description: Set preference of an existing interface (called by ioctl)
++ * Returns:
++ */
++void ma_ctl_set_preference(unsigned long);
++
++/*
++ * Public function: ma_ctl_add_iface
++ * Description: Inform control module to insert a new interface
++ * Returns: 0 if success, any other number means an error
++ */
++void ma_ctl_add_iface(int);
++
++/*
++ * Public function: ma_ctl_del_iface
++ * Description: Inform control module to remove an obsolete interface
++ * Returns: 0 if success, any other number means an error
++ */
++int ma_ctl_del_iface(int);
++
++/*
++ * Public function: ma_ctl_upd_iface
++ * Description: Inform control module of status change.
++ * Returns: 0 if success, any other number means an error
++ */
++int ma_ctl_upd_iface(int, int, int *);
++
++/*
++ * Public function: ma_ctl_init
++ * Description: XXX
++ * Returns: XXX
++ */
++void ma_ctl_init(void);
++
++/*
++ * Public function: ma_ctl_clean
++ * Description: XXX
++ * Returns: -
++ */
++void ma_ctl_clean(void);
++
++
++#endif
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/ndisc_ha.c linux-2.4.25/net/ipv6/mobile_ip6/ndisc_ha.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/ndisc_ha.c    1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/ndisc_ha.c        2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,596 @@
++/*
++ *    Mobile IPv6 Duplicate Address Detection Functions
++ *
++ *    Authors:
++ *    Krishna Kumar <krkumar@us.ibm.com>
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ *
++ */
++
++#include <linux/autoconf.h>
++#include <linux/types.h>
++#include <linux/init.h>
++#include <linux/skbuff.h>
++#include <linux/in6.h>
++#include <net/ipv6.h>
++#include <net/addrconf.h>
++#include <net/mipv6.h>
++
++#include "debug.h"
++#include "bcache.h"
++#include "ha.h" /* mipv6_generate_ll_addr */
++
++/*
++ * Binding Updates from MN are cached in this structure till DAD is performed.
++ * This structure is used to retrieve a pending Binding Update for the HA to
++ * reply to after performing DAD. The first cell is different from the rest as
++ * follows :
++ *    1. The first cell is used to chain the remaining cells. 
++ *    2. The timeout of the first cell is used to delete expired entries
++ *       in the list of cells, while the timeout of the other cells are
++ *       used for timing out a NS request so as to reply to a BU.
++ *    3. The only elements of the first cell that are used are :
++ *       next, prev, and callback_timer.
++ *
++ * TODO : Don't we need to do pneigh_lookup on the Link Local address ?
++ */
++struct mipv6_dad_cell {
++      /* Information needed for DAD management */
++      struct mipv6_dad_cell   *next;  /* Next element on the DAD list */
++      struct mipv6_dad_cell   *prev;  /* Prev element on the DAD list */
++      __u16                   probes; /* Number of times to probe for addr */
++      __u16                   flags;  /* Entry flags - see below */
++      struct timer_list       callback_timer; /* timeout for entry */
++
++      /* Information needed for performing DAD */
++      struct inet6_ifaddr     *ifp;
++      int                     ifindex;
++      struct in6_addr         daddr;
++      struct in6_addr         haddr;          /* home address */
++      struct in6_addr         ll_haddr;       /* Link Local value of haddr */
++      struct in6_addr         coa;
++      struct in6_addr         rep_coa;
++      __u32                   ba_lifetime;
++      __u16                   sequence;
++      __u8                    bu_flags;
++};
++
++/* Values for the 'flags' field in the mipv6_dad_cell */
++#define       DAD_INIT_ENTRY          0
++#define       DAD_DUPLICATE_ADDRESS   1
++#define       DAD_UNIQUE_ADDRESS      2
++
++/* Head of the pending DAD list */
++static struct mipv6_dad_cell dad_cell_head;
++
++/* Lock to access the pending DAD list */
++static rwlock_t dad_lock = RW_LOCK_UNLOCKED;
++
++/* Timer routine which deletes 'expired' entries in the DAD list */
++static void mipv6_dad_delete_old_entries(unsigned long unused)
++{
++      struct mipv6_dad_cell *curr, *next;
++      unsigned long next_time = 0;
++
++      write_lock(&dad_lock);
++      curr = dad_cell_head.next;
++      while (curr != &dad_cell_head) {
++              next = curr->next;
++              if (curr->flags != DAD_INIT_ENTRY) {
++                      if (curr->callback_timer.expires <= jiffies) {
++                              /* Entry has expired, free it up. */
++                              curr->next->prev = curr->prev;
++                              curr->prev->next = curr->next;
++                              in6_ifa_put(curr->ifp);
++                              kfree(curr);
++                      } else if (next_time <
++                                 curr->callback_timer.expires) {
++                              next_time = curr->callback_timer.expires;
++                      }
++              }
++              curr = next;
++      }
++      write_unlock(&dad_lock);
++      if (next_time) {
++              /*
++               * Start another timer if more cells need to be removed at
++               * a later stage.
++               */
++              dad_cell_head.callback_timer.expires = next_time;
++              add_timer(&dad_cell_head.callback_timer);
++      }
++}
++
++/* 
++ * Queue a timeout routine to clean up 'expired' DAD entries.
++ */
++static void mipv6_start_dad_head_timer(struct mipv6_dad_cell *cell)
++{
++      unsigned long expire = jiffies +
++          cell->ifp->idev->nd_parms->retrans_time * 10;
++
++      if (!timer_pending(&dad_cell_head.callback_timer) ||
++          expire < dad_cell_head.callback_timer.expires) {
++              /*
++               * Add timer if none pending, or mod the timer if new 
++               * cell needs to be expired before existing timer runs.
++               *
++               * We let the cell remain as long as possible, so that
++               * new BU's as part of retransmissions don't have to go
++               * through DAD before replying.
++               */
++              dad_cell_head.callback_timer.expires = expire;
++
++              /*
++               * Keep the cell around for atleast some time to handle
++               * retransmissions or BU's due to fast MN movement. This
++               * is needed otherwise a previous timeout can delete all
++               * expired entries including this new one.
++               */
++              cell->callback_timer.expires = jiffies +
++                  cell->ifp->idev->nd_parms->retrans_time * 5;
++              if (!timer_pending(&dad_cell_head.callback_timer)) {
++                      add_timer(&dad_cell_head.callback_timer);
++              } else {
++                      mod_timer(&dad_cell_head.callback_timer, expire);
++              }
++      }
++}
++
++
++/* Join solicited node MC address */
++static inline void mipv6_join_sol_mc_addr(struct in6_addr *addr,
++                                        struct net_device *dev)
++{
++      struct in6_addr maddr;
++
++      /* Join solicited node MC address */
++      addrconf_addr_solict_mult(addr, &maddr);
++      ipv6_dev_mc_inc(dev, &maddr);
++}
++
++/* Leave solicited node MC address */
++static inline void mipv6_leave_sol_mc_addr(struct in6_addr *addr,
++                                         struct net_device *dev)
++{
++      struct in6_addr maddr;
++
++      addrconf_addr_solict_mult(addr, &maddr);
++      ipv6_dev_mc_dec(dev, &maddr);
++}
++
++/* Send a NS */
++static inline void mipv6_dad_send_ns(struct inet6_ifaddr *ifp,
++                                   struct in6_addr *haddr)
++{
++      struct in6_addr unspec;
++      struct in6_addr mcaddr;
++
++      ipv6_addr_set(&unspec, 0, 0, 0, 0);
++      addrconf_addr_solict_mult(haddr, &mcaddr);
++
++      /* addr is 'unspec' since we treat this address as transient */
++      ndisc_send_ns(ifp->idev->dev, NULL, haddr, &mcaddr, &unspec);
++}
++
++/*
++ * Search for a home address in the list of pending DAD's. Called from
++ * Neighbor Advertisement
++ * Return values :
++ *    -1 : No DAD entry found for this advertisement, or entry already
++ *         finished processing.
++ *    0  : Entry found waiting for DAD to finish.
++ */
++static int dad_search_haddr(struct in6_addr *ll_haddr,
++                          struct in6_addr *daddr, struct in6_addr *haddr,
++                          struct in6_addr *coa, struct in6_addr *rep_coa,
++                          __u16 * seq, struct inet6_ifaddr **ifp)
++{
++      struct mipv6_dad_cell *cell;
++
++      read_lock(&dad_lock);
++      cell = dad_cell_head.next;
++      while (cell != &dad_cell_head &&
++             ipv6_addr_cmp(&cell->ll_haddr, ll_haddr) && 
++             ipv6_addr_cmp(&cell->haddr, ll_haddr)) {
++              cell = cell->next;
++      }
++      if (cell == &dad_cell_head || cell->flags != DAD_INIT_ENTRY) {
++              /* Not found element, or element already finished processing */
++              if (cell != &dad_cell_head) {
++                      /*
++                       * Set the state to DUPLICATE, even if it was UNIQUE
++                       * earlier. It is not needed to setup timer via 
++                       * mipv6_start_dad_head_timer since this must have
++                       * already been done.
++                       */
++                      cell->flags = DAD_DUPLICATE_ADDRESS;
++              }
++              read_unlock(&dad_lock);
++              return -1;
++      }
++
++      /*
++       * The NA found an unprocessed entry in the DAD list. Expire this
++       * entry since another node advertised this address. Caller should
++       * reject BU (DAD failed).
++       */
++      ipv6_addr_copy(daddr, &cell->daddr);
++      ipv6_addr_copy(haddr, &cell->haddr);
++      ipv6_addr_copy(coa, &cell->coa);
++      ipv6_addr_copy(rep_coa, &cell->rep_coa);
++      *seq = cell->sequence;
++      *ifp = cell->ifp;
++
++      if (del_timer(&cell->callback_timer) == 0) {
++              /* Timer already deleted, race with Timeout Handler */
++              /* No action needed */
++      }
++
++      cell->flags = DAD_DUPLICATE_ADDRESS;
++
++      /* Now leave this address to avoid future processing of NA's */
++      mipv6_leave_sol_mc_addr(&cell->ll_haddr, cell->ifp->idev->dev);
++      /* Leave also global address, if link local address was in use */
++      if (ipv6_addr_cmp(&cell->ll_haddr, &cell->haddr))
++              mipv6_leave_sol_mc_addr(&cell->haddr, cell->ifp->idev->dev);
++      /* Start dad_head timer to remove this entry */
++      mipv6_start_dad_head_timer(cell);
++
++      read_unlock(&dad_lock);
++
++      return 0;
++}
++
++/* ENTRY routine called via Neighbor Advertisement */
++void mipv6_check_dad(struct in6_addr *ll_haddr)
++{
++      struct in6_addr daddr, haddr, coa, rep_coa;
++      struct inet6_ifaddr *ifp;
++      __u16 seq;
++
++      if (dad_search_haddr(ll_haddr, &daddr, &haddr, &coa, &rep_coa, &seq,
++                           &ifp) < 0) {
++              /* 
++               * Didn't find entry, or no action needed (the action has
++               * already been performed).
++               */
++              return;
++      }
++
++      /*
++       * A DAD cell was present, meaning that there is a pending BU
++       * request for 'haddr' - reject the BU.
++       */
++      mipv6_bu_finish(ifp, 0, DUPLICATE_ADDR_DETECT_FAIL,
++                      &daddr, &haddr, &coa, &rep_coa, 0, seq, 0, NULL);
++      return;
++}
++
++/*
++ * Check if the passed 'cell' is in the list of pending DAD's. Called from
++ * the Timeout Handler.
++ *
++ * Assumes that the caller is holding the dad_lock in reader mode.
++ */
++static int dad_search_cell(struct mipv6_dad_cell *cell)
++{
++      struct mipv6_dad_cell *tmp;
++
++      tmp = dad_cell_head.next;
++      while (tmp != &dad_cell_head && tmp != cell) {
++              tmp = tmp->next;
++      }
++      if (tmp == cell) {
++              if (cell->flags == DAD_INIT_ENTRY) {
++                      /* Found valid entry */
++                      if (--cell->probes == 0) {
++                              /*
++                               * Retransmission's are over - return success.
++                               */
++                              cell->flags = DAD_UNIQUE_ADDRESS;
++
++                              /* 
++                               * Leave this address to avoid future 
++                               * processing of NA's.
++                               */
++                              mipv6_leave_sol_mc_addr(&cell->ll_haddr,
++                                                      cell->ifp->idev->
++                                                      dev);
++                              if (ipv6_addr_cmp(&cell->ll_haddr, &cell->haddr))
++                                      mipv6_leave_sol_mc_addr(&cell->haddr, 
++                                                              cell->ifp->idev->dev);
++                              /* start timeout to delete this cell. */
++                              mipv6_start_dad_head_timer(cell);
++                              return 0;
++                      }
++                      /*
++                       * Retransmission not finished, send another NS and
++                       * return failure.
++                       */
++                      mipv6_dad_send_ns(cell->ifp, &cell->ll_haddr);
++                      if (ipv6_addr_cmp(&cell->ll_haddr, &cell->haddr))
++                              mipv6_leave_sol_mc_addr(&cell->haddr, 
++                                                      cell->ifp->idev->dev);
++                      cell->callback_timer.expires = jiffies +
++                          cell->ifp->idev->nd_parms->retrans_time;
++                      add_timer(&cell->callback_timer);
++              } else {
++                      /*
++                       * This means that an NA was received before the
++                       * timeout and when the state changed from
++                       * DAD_INIT_ENTRY, the BU got failed as a result.
++                       * There is nothing to be done.
++                       */
++              }
++      }
++      return -1;
++}
++
++/* ENTRY routine called via Timeout */
++static void mipv6_dad_timeout(unsigned long arg)
++{
++      __u8 ba_status = SUCCESS;
++      struct in6_addr daddr;
++      struct in6_addr haddr;
++      struct in6_addr coa;
++      struct in6_addr rep_coa;
++      struct inet6_ifaddr *ifp;
++      int ifindex;
++      __u32 ba_lifetime;
++      __u16 sequence;
++      __u8 flags;
++      struct mipv6_dad_cell *cell = (struct mipv6_dad_cell *) arg;
++
++      /*
++       * If entry is not in the list, we have already sent BU Failure
++       * after getting a NA.
++       */
++      read_lock(&dad_lock);
++      if (dad_search_cell(cell) < 0) {
++              /*
++               * 'cell' is no longer valid (may not be in the list or
++               * is already processed, due to NA processing), or NS
++               * retransmissions are not yet over.
++               */
++              read_unlock(&dad_lock);
++              return;
++      }
++
++      /* This is the final Timeout. Send Bind Ack Success */
++
++      ifp = cell->ifp;
++      ifindex = cell->ifindex;
++      ba_lifetime = cell->ba_lifetime;
++      sequence = cell->sequence;
++      flags = cell->bu_flags;
++
++      ipv6_addr_copy(&daddr, &cell->daddr);
++      ipv6_addr_copy(&haddr, &cell->haddr);
++      ipv6_addr_copy(&coa, &cell->coa);
++      ipv6_addr_copy(&rep_coa, &cell->rep_coa);
++      read_unlock(&dad_lock);
++
++      /* Send BU Acknowledgement Success */
++      mipv6_bu_finish(ifp, ifindex, ba_status, 
++                      &daddr, &haddr, &coa, &rep_coa,
++                      ba_lifetime, sequence, flags, NULL);
++      return;
++}
++
++/*
++ * Check if original home address exists in our DAD pending list, if so return
++ * the cell.
++ *
++ * Assumes that the caller is holding the dad_lock in writer mode.
++ */
++static struct mipv6_dad_cell *mipv6_dad_get_cell(struct in6_addr *haddr)
++{
++      struct mipv6_dad_cell *cell;
++
++      cell = dad_cell_head.next;
++      while (cell != &dad_cell_head
++             && ipv6_addr_cmp(&cell->haddr, haddr)) {
++              cell = cell->next;
++      }
++      if (cell == &dad_cell_head) {
++              /* Not found element */
++              return NULL;
++      }
++      return cell;
++}
++
++/*
++ * Save all parameters needed for doing a Bind Ack in the mipv6_dad_cell 
++ * structure.
++ */
++static void mipv6_dad_save_cell(struct mipv6_dad_cell *cell,
++                              struct inet6_ifaddr *ifp, int ifindex,
++                              struct in6_addr *daddr,
++                              struct in6_addr *haddr,
++                              struct in6_addr *coa, 
++                              struct in6_addr *rep_coa,
++                              __u32 ba_lifetime,
++                              __u16 sequence, __u8 flags)
++{
++      in6_ifa_hold(ifp);
++      cell->ifp = ifp;
++      cell->ifindex = ifindex;
++
++      ipv6_addr_copy(&cell->daddr, daddr);
++      ipv6_addr_copy(&cell->haddr, haddr);
++      ipv6_addr_copy(&cell->coa, coa);
++      ipv6_addr_copy(&cell->rep_coa, rep_coa);
++
++      /* Convert cell->ll_haddr to Link Local address */
++      if (flags & MIPV6_BU_F_LLADDR) 
++              mipv6_generate_ll_addr(&cell->ll_haddr, haddr);
++      else 
++              ipv6_addr_copy(&cell->ll_haddr, haddr);
++
++      cell->ba_lifetime = ba_lifetime;
++      cell->sequence = sequence;
++      cell->bu_flags = flags;
++}
++
++/*
++ * Top level DAD routine for performing DAD.
++ *
++ * Return values
++ *    0     : Don't need to do DAD.
++ *    1     : Need to do DAD.
++ *    -n    : Error, where 'n' is the reason for the error.
++ *
++ * Assumption : DAD process has been optimized by using cached values upto
++ * some time. However sometimes this can cause problems. Eg. when the first
++ * BU was received, DAD might have failed. Before the second BU arrived,
++ * the node using MN's home address might have stopped using it, but still
++ * we will return DAD_DUPLICATE_ADDRESS based on the first DAD's result. Or 
++ * this can go the other way around. However, it is a very small possibility
++ * and thus optimization is turned on by default. It is possible to change
++ * this feature (needs a little code-rewriting in this routine), but 
++ * currently DAD result is being cached for performance reasons.
++ */
++int mipv6_dad_start(struct inet6_ifaddr *ifp, int ifindex,
++                  struct in6_addr *daddr, struct in6_addr *haddr,
++                  struct in6_addr *coa, struct in6_addr *rep_coa,
++                  __u32 ba_lifetime, __u16 sequence, __u8 flags)
++{
++      int found;
++      struct mipv6_dad_cell *cell;
++      struct mipv6_bce bc_entry;
++
++      if (ifp->idev->cnf.dad_transmits == 0) {
++              /* DAD is not configured on the HA, return SUCCESS */
++              return 0;
++      }
++
++      if (mipv6_bcache_get(haddr, daddr, &bc_entry) == 0) {
++              /*
++               * We already have an entry in our cache - don't need to 
++               * do DAD as we are already defending this home address.
++               */
++              return 0;
++      }
++
++      write_lock(&dad_lock);
++      if ((cell = mipv6_dad_get_cell(haddr)) != NULL) {
++              /*
++               * An existing entry for BU was found in our cache due
++               * to retransmission of the BU or a new COA registration.
++               */
++              switch (cell->flags) {
++              case DAD_INIT_ENTRY:
++                      /* Old entry is waiting for DAD to complete */
++                      break;
++              case DAD_UNIQUE_ADDRESS:
++                      /* DAD is finished successfully - return success. */
++                      write_unlock(&dad_lock);
++                      return 0;
++              case DAD_DUPLICATE_ADDRESS:
++                      /*
++                       * DAD is finished and we got a NA while doing BU -
++                       * return failure.
++                       */
++                      write_unlock(&dad_lock);
++                      return -DUPLICATE_ADDR_DETECT_FAIL;
++              default:
++                      /* Unknown state - should never happen */
++                      DEBUG(DBG_WARNING,
++                            "cell entry in unknown state : %d",
++                            cell->flags);
++                      write_unlock(&dad_lock);
++                      return -REASON_UNSPECIFIED;
++              }
++              found = 1;
++      } else {
++              if ((cell = (struct mipv6_dad_cell *)
++                   kmalloc(sizeof(struct mipv6_dad_cell), GFP_ATOMIC))
++                  == NULL) {
++                      return -INSUFFICIENT_RESOURCES;
++              }
++              found = 0;
++      }
++
++      mipv6_dad_save_cell(cell, ifp, ifindex, daddr, haddr, coa, rep_coa,
++                          ba_lifetime, sequence, flags);
++
++      if (!found) {
++              cell->flags = DAD_INIT_ENTRY;
++              cell->probes = ifp->idev->cnf.dad_transmits;
++
++              /* Insert element on dad_cell_head list */
++              dad_cell_head.prev->next = cell;
++              cell->next = &dad_cell_head;
++              cell->prev = dad_cell_head.prev;
++              dad_cell_head.prev = cell;
++              write_unlock(&dad_lock);
++              if (flags & MIPV6_BU_F_LLADDR) {
++                      /* join the solicited node MC of the global homeaddr.*/
++                      mipv6_join_sol_mc_addr(&cell->haddr, ifp->idev->dev);
++                      /* Send a NS */
++                      mipv6_dad_send_ns(ifp, &cell->haddr);
++              }
++              /* join the solicited node MC of the homeaddr. */
++              mipv6_join_sol_mc_addr(&cell->ll_haddr, ifp->idev->dev);
++              
++              /* Send a NS */
++              mipv6_dad_send_ns(ifp, &cell->ll_haddr);
++              
++              /* Initialize timer for this cell to timeout the NS. */
++              init_timer(&cell->callback_timer);
++              cell->callback_timer.data = (unsigned long) cell;
++              cell->callback_timer.function = mipv6_dad_timeout;
++              cell->callback_timer.expires = jiffies +
++                  ifp->idev->nd_parms->retrans_time;
++              add_timer(&cell->callback_timer);
++      } else {
++              write_unlock(&dad_lock);
++      }
++      return 1;
++}
++
++void __init mipv6_dad_init(void)
++{
++      dad_cell_head.next = dad_cell_head.prev = &dad_cell_head;
++      init_timer(&dad_cell_head.callback_timer);
++      dad_cell_head.callback_timer.data = 0;
++      dad_cell_head.callback_timer.function =
++              mipv6_dad_delete_old_entries;
++}
++
++void __exit mipv6_dad_exit(void)
++{
++      struct mipv6_dad_cell *curr, *next;
++
++      write_lock_bh(&dad_lock);
++      del_timer(&dad_cell_head.callback_timer);
++
++      curr = dad_cell_head.next;
++      while (curr != &dad_cell_head) {
++              next = curr->next;
++              del_timer(&curr->callback_timer);
++              if (curr->flags == DAD_INIT_ENTRY) {
++                      /*
++                       * We were in DAD_INIT state and listening to the
++                       * solicited node MC address - need to stop that.
++                       */
++                      mipv6_leave_sol_mc_addr(&curr->ll_haddr,
++                                              curr->ifp->idev->dev);
++                      if (ipv6_addr_cmp(&curr->ll_haddr, &curr->haddr))
++                              mipv6_leave_sol_mc_addr(&curr->haddr, 
++                                                      curr->ifp->idev->dev);
++              }
++              in6_ifa_put(curr->ifp);
++              kfree(curr);
++              curr = next;
++      }
++      dad_cell_head.next = dad_cell_head.prev = &dad_cell_head;
++      write_unlock_bh(&dad_lock);
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/prefix.c linux-2.4.25/net/ipv6/mobile_ip6/prefix.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/prefix.c      1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/prefix.c  2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,217 @@
++/**
++ * Prefix solicitation and advertisement
++ *
++ * Authors:
++ * Jaakko Laine <medved@iki.fi>
++ *
++ * $Id$
++ *
++ * 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 (at your option) any later version.
++ */
++
++#include <linux/config.h>
++#include <linux/icmpv6.h>
++#include <linux/net.h>
++#include <linux/spinlock.h>
++#include <linux/timer.h>
++#include <linux/netdevice.h>
++#include <net/ipv6.h>
++#include <net/addrconf.h>
++#include <net/ip6_route.h>
++#include <net/mipv6.h>
++
++#include "mipv6_icmp.h"
++#include "debug.h"
++#include "sortedlist.h"
++#include "prefix.h"
++#include "config.h"
++
++#define INFINITY 0xffffffff
++
++struct timer_list pfx_timer;
++
++struct list_head pfx_list;
++rwlock_t pfx_list_lock = RW_LOCK_UNLOCKED;
++
++int compare_pfx_list_entry(const void *data1, const void *data2,
++                         int datalen)
++{
++      struct pfx_list_entry *e1 = (struct pfx_list_entry *) data1;
++      struct pfx_list_entry *e2 = (struct pfx_list_entry *) data2;
++
++      return ((ipv6_addr_cmp(&e1->daddr, &e2->daddr) == 0)
++              && (e2->ifindex == -1 || e1->ifindex == e2->ifindex));
++}
++
++/**
++ * mipv6_pfx_cancel_send - cancel pending items to daddr from saddr
++ * @daddr: Destination address
++ * @ifindex: pending items on this interface will be canceled
++ *
++ * if ifindex == -1, all items to daddr will be removed
++ */
++void mipv6_pfx_cancel_send(struct in6_addr *daddr, int ifindex)
++{
++      unsigned long tmp;
++      struct pfx_list_entry entry;
++
++      DEBUG_FUNC();
++
++      /* We'll just be comparing these parts... */
++      memcpy(&entry.daddr, daddr, sizeof(struct in6_addr));
++      entry.ifindex = ifindex;
++
++      write_lock_bh(&pfx_list_lock);
++
++      while (mipv6_slist_del_item(&pfx_list, &entry,
++                                  compare_pfx_list_entry) == 0)
++              ;
++
++      if ((tmp = mipv6_slist_get_first_key(&pfx_list)))
++              mod_timer(&pfx_timer, tmp);
++
++      write_unlock_bh(&pfx_list_lock);
++}
++
++/**
++ * mipv6_pfx_add_ha - add a new HA to send prefix solicitations to
++ * @daddr: address of HA
++ * @saddr: our address to use as source address
++ * @ifindex: interface index
++ */
++void mipv6_pfx_add_ha(struct in6_addr *daddr, struct in6_addr *saddr,
++                    int ifindex)
++{
++      unsigned long tmp;
++      struct pfx_list_entry entry;
++
++      DEBUG_FUNC();
++
++      memcpy(&entry.daddr, daddr, sizeof(struct in6_addr));
++      memcpy(&entry.saddr, saddr, sizeof(struct in6_addr));
++      entry.retries = 0;
++      entry.ifindex = ifindex;
++
++      write_lock_bh(&pfx_list_lock);
++      if (mipv6_slist_modify(&pfx_list, &entry, sizeof(struct pfx_list_entry),
++                             jiffies + INITIAL_SOLICIT_TIMER * HZ,
++                             compare_pfx_list_entry))
++              DEBUG(DBG_WARNING, "Cannot add new HA to pfx list");
++
++      if ((tmp = mipv6_slist_get_first_key(&pfx_list)))
++              mod_timer(&pfx_timer, tmp);
++      write_unlock_bh(&pfx_list_lock);
++}
++
++int mipv6_pfx_add_home(int ifindex, struct in6_addr *saddr, 
++                     struct in6_addr *daddr, unsigned long min_expire)
++{
++      unsigned long tmp;
++
++      write_lock(&pfx_list_lock);
++
++      if (min_expire != INFINITY) {
++              unsigned long expire;
++              struct pfx_list_entry entry;
++              
++              memcpy(&entry.daddr, saddr, sizeof(struct in6_addr));
++              memcpy(&entry.saddr, daddr, sizeof(struct in6_addr));
++              entry.retries = 0;
++              entry.ifindex = ifindex;
++
++              /* This is against the RFC 3775, but we need to set
++               * a minimum interval for a prefix solicitation.
++               * Otherwise a prefix solicitation storm will
++               * result if valid lifetime of the prefix is
++               * smaller than MAX_PFX_ADV_DELAY
++               */
++              min_expire -= MAX_PFX_ADV_DELAY;
++              min_expire = min_expire < MIN_PFX_SOL_DELAY ? MIN_PFX_SOL_DELAY : min_expire;
++
++              expire = jiffies + min_expire * HZ;
++
++              if (mipv6_slist_modify(&pfx_list, &entry,
++                                     sizeof(struct pfx_list_entry),
++                                     expire,
++                                     compare_pfx_list_entry) != 0)
++                      DEBUG(DBG_WARNING, "Cannot add new entry to pfx_list");
++      }
++
++      if ((tmp = mipv6_slist_get_first_key(&pfx_list)))
++              mod_timer(&pfx_timer, tmp);
++
++      write_unlock(&pfx_list_lock);
++
++      return 0;
++}
++
++/**
++ * set_ha_pfx_list - manipulate pfx_list for HA when timer goes off
++ * @entry: pfx_list_entry that is due
++ */
++static void set_ha_pfx_list(struct pfx_list_entry *entry)
++{
++}
++
++/**
++ * set_mn_pfx_list - manipulate pfx_list for MN when timer goes off
++ * @entry: pfx_list_entry that is due
++ */
++static void set_mn_pfx_list(struct pfx_list_entry *entry)
++{
++}
++
++/**
++ * pfx_timer_handler - general timer handler
++ * @dummy: dummy
++ *
++ * calls set_ha_pfx_list and set_mn_pfx_list to do the thing when
++ * a timer goes off
++ */
++static void pfx_timer_handler(unsigned long dummy)
++{
++      unsigned long tmp;
++      struct pfx_list_entry *entry;
++
++      DEBUG_FUNC();
++
++      write_lock(&pfx_list_lock);
++      if (!(entry = mipv6_slist_get_first(&pfx_list)))
++              goto out;
++
++      if (mip6node_cnf.capabilities & CAP_HA)
++              set_ha_pfx_list(entry);
++      if (mip6node_cnf.capabilities & CAP_MN)
++              set_mn_pfx_list(entry);
++      if ((tmp = mipv6_slist_get_first_key(&pfx_list)))
++              mod_timer(&pfx_timer, tmp);
++
++ out:
++      write_unlock(&pfx_list_lock);
++}
++
++int mipv6_initialize_pfx_icmpv6(void)
++{
++      INIT_LIST_HEAD(&pfx_list);
++
++      init_timer(&pfx_timer);
++      pfx_timer.function = pfx_timer_handler;
++
++      return 0;
++}
++
++void mipv6_shutdown_pfx_icmpv6(void)
++{
++      struct prefix_info *tmp;
++
++      if (timer_pending(&pfx_timer))
++              del_timer(&pfx_timer);
++
++      write_lock_bh(&pfx_list_lock);
++      while ((tmp = mipv6_slist_del_first(&pfx_list)))
++              kfree(tmp);
++      write_unlock_bh(&pfx_list_lock);
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/prefix.h linux-2.4.25/net/ipv6/mobile_ip6/prefix.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/prefix.h      1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/prefix.h  2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,57 @@
++/*
++ *      MIPL Mobile IPv6 Prefix solicitation and advertisement
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _PREFIX_H
++#define _PREFIX_H
++
++#include <net/addrconf.h>
++
++struct pfx_list_entry {
++      struct in6_addr daddr;
++      struct in6_addr saddr;
++      int retries;
++      int ifindex;
++};
++
++extern struct list_head pfx_list;
++extern rwlock_t pfx_list_lock;
++extern struct timer_list pfx_timer;
++
++int compare_pfx_list_entry(const void *data1, const void *data2,
++                         int datalen);
++
++/**
++ * mipv6_pfx_cancel_send - cancel pending pfx_advs/sols to daddr
++ * @daddr: destination address
++ * @ifindex: pending items on this interface will be canceled
++ *
++ * if ifindex == -1, all items to daddr will be removed
++ */
++void mipv6_pfx_cancel_send(struct in6_addr *daddr, int ifindex);
++
++/**
++ * mipv6_pfx_add_ha - add a new HA to send prefix solicitations to
++ * @daddr: address of HA
++ * @saddr: our address to use as source address
++ * @ifindex: interface index
++ */
++void mipv6_pfx_add_ha(struct in6_addr *daddr, struct in6_addr *saddr,
++                    int ifindex);
++
++void mipv6_pfxs_modified(struct prefix_info *pinfo, int ifindex);
++
++int mipv6_pfx_add_home(int ifindex, struct in6_addr *daddr,
++                     struct in6_addr *saddr, unsigned long min_expire);
++
++int mipv6_initialize_pfx_icmpv6(void);
++void mipv6_shutdown_pfx_icmpv6(void);
++
++#endif
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/prefix_ha.c linux-2.4.25/net/ipv6/mobile_ip6/prefix_ha.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/prefix_ha.c   1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/prefix_ha.c       2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,122 @@
++/**
++ * Prefix advertisement for Home Agent
++ *
++ * Authors:
++ * Jaakko Laine <medved@iki.fi>
++ *
++ * $Id$
++ *
++ * 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 (at your option) any later version.
++ */
++
++#include <linux/config.h>
++#include <linux/icmpv6.h>
++#include <linux/net.h>
++#include <linux/spinlock.h>
++#include <linux/timer.h>
++#include <linux/netdevice.h>
++#include <net/ipv6.h>
++#include <net/addrconf.h>
++#include <net/ip6_route.h>
++#include <net/mipv6.h>
++
++#include "mipv6_icmp.h"
++#include "debug.h"
++#include "sortedlist.h"
++#include "util.h"
++#include "bcache.h"
++#include "config.h"
++#include "prefix.h"
++
++/**
++ * pfx_adv_iterator - modify pfx_list entries according to new prefix info
++ * @data: MN's home registration bcache_entry
++ * @args: new prefix info
++ * @sortkey: ignored
++ */
++static int pfx_adv_iterator(void *data, void *args, unsigned long sortkey)
++{
++      struct mipv6_bce *bc_entry = (struct mipv6_bce *) data;
++      struct prefix_info *pinfo = (struct prefix_info *) args;
++
++      if (mipv6_prefix_compare(&bc_entry->coa, &pinfo->prefix,
++                               pinfo->prefix_len) == 0) {
++              struct pfx_list_entry pfx_entry;
++
++              memcpy(&pfx_entry.daddr, &bc_entry->coa,
++                     sizeof(struct in6_addr));
++              memcpy(&pfx_entry.daddr, &bc_entry->our_addr,
++                     sizeof(struct in6_addr));
++              pfx_entry.retries = 0;
++              pfx_entry.ifindex = bc_entry->ifindex;
++
++              mipv6_slist_modify(&pfx_list, &pfx_entry,
++                                 sizeof(struct pfx_list_entry),
++                                 jiffies +
++                                 net_random() % (MAX_PFX_ADV_DELAY * HZ),
++                                 compare_pfx_list_entry);
++      }
++
++      return 0;
++}
++
++struct homereg_iterator_args {
++      struct list_head *head;
++      int count;
++};
++
++static int homereg_iterator(void *data, void *args, unsigned long *sortkey)
++{
++      struct mipv6_bce *entry = (struct mipv6_bce *) data;
++      struct homereg_iterator_args *state =
++              (struct homereg_iterator_args *) args;
++
++      if (entry->type == HOME_REGISTRATION) {
++              mipv6_slist_add(state->head, entry,
++                              sizeof(struct mipv6_bce),
++                              state->count);
++              state->count++;
++      }
++      return 0;
++}
++
++static int mipv6_bcache_get_homeregs(struct list_head *head)
++{
++      struct homereg_iterator_args args;
++
++      DEBUG_FUNC();
++
++      args.count = 0;
++      args.head = head;
++
++      mipv6_bcache_iterate(homereg_iterator, &args);
++      return args.count;
++}
++
++/**
++ * mipv6_prefix_added - prefix was added to interface, act accordingly
++ * @pinfo: prefix_info that was added
++ * @ifindex: interface index
++ */
++void mipv6_pfxs_modified(struct prefix_info *pinfo, int ifindex)
++{
++      int count;
++      unsigned long tmp;
++      struct list_head home_regs;
++
++      DEBUG_FUNC();
++
++      INIT_LIST_HEAD(&home_regs);
++
++      if (!(count = mipv6_bcache_get_homeregs(&home_regs)))
++              return;
++
++      write_lock_bh(&pfx_list_lock);
++      mipv6_slist_for_each(&home_regs, pinfo, pfx_adv_iterator);
++      if ((tmp = mipv6_slist_get_first_key(&pfx_list)))
++              mod_timer(&pfx_timer, tmp);
++      write_unlock_bh(&pfx_list_lock);
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/rr_crypto.c linux-2.4.25/net/ipv6/mobile_ip6/rr_crypto.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/rr_crypto.c   1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/rr_crypto.c       2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,255 @@
++/*
++ *      rr_cookie.c - Mobile IPv6 return routability crypto  
++ *      Author :  Henrik Petander <henrik.petander@hut.fi>
++ * 
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ *
++ *
++ *
++ */
++
++#include <linux/kernel.h>
++#include <linux/types.h>
++#include <linux/spinlock.h>
++#include <linux/sched.h>
++#include <linux/timer.h>
++#include <linux/in6.h>
++#include <linux/init.h>
++#include <linux/random.h>
++
++#include <net/ipv6.h>
++
++#include "debug.h"
++#include "hmac.h"
++#include "rr_crypto.h"
++
++#define DBG_RR 5
++
++u8 k_CN[HMAC_SHA1_KEY_SIZE]; // secret key of CN 
++
++u16 curr_index = 0;
++
++struct nonce_timestamp nonce_table[MAX_NONCES];
++spinlock_t nonce_lock = SPIN_LOCK_UNLOCKED;
++void update_nonces(void);
++
++/** nonce_is_fresh - whether the nonce was generated recently
++ *  
++ * @non_ts : table entry containing the nonce and a timestamp
++ * @interval : if nonce was generated within interval seconds it is fresh
++ *
++ * Returns 1 if the nonce is fresh, 0 otherwise.
++ */
++static int nonce_is_fresh(struct nonce_timestamp *non_ts, unsigned long interval)
++{
++      if (time_before(jiffies, non_ts->timestamp + interval * HZ) && !non_ts->invalid)
++              return 1;
++      return 0;
++}
++void mipv6_rr_invalidate_nonce(u16 nonce_ind)
++{
++      spin_lock_bh(&nonce_lock);
++      if (nonce_ind > MAX_NONCES) {
++              spin_unlock_bh(&nonce_lock);
++              return;
++      }
++      nonce_table[nonce_ind].invalid = 1;
++      spin_unlock_bh(&nonce_lock);
++}
++/* Returns a pointer to a new nonce  */
++struct mipv6_rr_nonce * mipv6_rr_get_new_nonce(void)
++{
++      struct mipv6_rr_nonce *nce = kmalloc(sizeof(*nce), GFP_ATOMIC);
++
++      if (!nce)
++              return NULL;
++      // Lock nonces here
++      spin_lock_bh(&nonce_lock);
++      // If nonce is not fresh create new one 
++      if (!nonce_is_fresh(&nonce_table[curr_index], MIPV6_RR_NONCE_LIFETIME)) {
++              // increment the last nonce pointer and create new nonce
++              curr_index++;
++              // Wrap around
++              if (curr_index == MAX_NONCES)
++                      curr_index = 0;
++              // Get random data to fill the nonce data
++              get_random_bytes(nonce_table[curr_index].nonce.data, MIPV6_RR_NONCE_DATA_LENGTH);
++              // Fill the index field
++              nonce_table[curr_index].nonce.index = curr_index;
++              nonce_table[curr_index].invalid = 0;
++              nonce_table[curr_index].timestamp = jiffies;
++      }
++      spin_unlock_bh(&nonce_lock);
++      memcpy(nce, &nonce_table[curr_index].nonce, sizeof(*nce));
++      // Unlock nonces
++      return nce;
++}
++/** mipv6_rr_nonce_get_by_index - returns a nonce for index 
++ * @nonce_ind : index of the nonce
++ *
++ * Returns a nonce or NULL if the nonce index was invalid or the nonce 
++ * for the index was not fresh.
++ */
++struct mipv6_rr_nonce * mipv6_rr_nonce_get_by_index(u16 nonce_ind)
++{
++      struct mipv6_rr_nonce *nce = NULL;
++      
++      spin_lock_bh(&nonce_lock);
++      if (nonce_ind >= MAX_NONCES) {
++              DEBUG(DBG_WARNING, "Nonce index field from BU invalid");
++
++              /* Here a double of the nonce_lifetime is used for freshness 
++               * verification, since the nonces 
++               * are not created in response to every initiator packet
++               */
++      } else if (nonce_is_fresh(&nonce_table[nonce_ind], 2 * MIPV6_RR_NONCE_LIFETIME)) {
++              nce = kmalloc(sizeof(*nce), GFP_ATOMIC);
++              memcpy(nce, &nonce_table[nonce_ind].nonce, sizeof(*nce));
++      }
++      spin_unlock_bh(&nonce_lock);
++
++      return nce;
++}
++
++/* Fills rr test init cookies with random bytes */  
++void mipv6_rr_mn_cookie_create(u8 *cookie)
++{
++      get_random_bytes(cookie, MIPV6_RR_COOKIE_LENGTH);
++}
++
++/** mipv6_rr_cookie_create - builds a home or care-of cookie
++ * 
++ * @addr : the home or care-of address from HoTI or CoTI
++ * @ckie : memory where the cookie is copied to
++ * @nce : pointer to a nonce used for the calculation, nce is freed during the function
++ *
++ */
++int mipv6_rr_cookie_create(struct in6_addr *addr, u8 **ckie,
++              u16 nonce_index)
++{
++      struct ah_processing ah_proc;
++      u8 digest[HMAC_SHA1_HASH_LEN];
++      struct mipv6_rr_nonce *nce;
++
++      if ((nce = mipv6_rr_nonce_get_by_index(nonce_index))== NULL)
++              return -1;
++
++      if (*ckie == NULL && (*ckie = kmalloc(MIPV6_RR_COOKIE_LENGTH,
++                                      GFP_ATOMIC)) == NULL) {
++              kfree(nce);
++              return -1;
++      }
++      /* Calculate the full hmac-sha1 digest from address and nonce using the secret key of cn */
++      
++      if (ah_hmac_sha1_init(&ah_proc, k_CN, HMAC_SHA1_KEY_SIZE) < 0) {
++              DEBUG(DBG_ERROR, "Hmac sha1 initialization failed");
++              kfree(nce);
++              return -1;
++      }
++
++      ah_hmac_sha1_loop(&ah_proc, addr, sizeof(*addr));
++      ah_hmac_sha1_loop(&ah_proc, nce->data,  MIPV6_RR_NONCE_DATA_LENGTH);
++      ah_hmac_sha1_result(&ah_proc, digest);
++
++      
++      /* clean up nonce */
++      kfree(nce);
++
++      /* Copy first 64 bits of hash target to the cookie */ 
++      memcpy(*ckie, digest, MIPV6_RR_COOKIE_LENGTH);
++      return 0;
++}
++
++/** mipv6_rr_key_calc - creates BU authentication key
++ * 
++ * @hoc : Home Cookie 
++ * @coc : Care-of Cookie 
++ * 
++ * Returns BU authentication key of length HMAC_SHA1_KEY_SIZE  or NULL in error cases, 
++ * caller needs to free the key.
++ */
++u8 *mipv6_rr_key_calc(u8 *hoc, u8 *coc)
++{
++      
++      u8 *key_bu = kmalloc(HMAC_SHA1_KEY_SIZE, GFP_ATOMIC);
++      SHA1_CTX c;
++
++      if (!key_bu) {
++              DEBUG(DBG_CRITICAL, "Memory allocation failed, could nort create BU authentication key");
++              return NULL;
++      }
++
++      /* Calculate the key from home and care-of cookies 
++       * Kbu = sha1(home_cookie | care-of cookie) 
++       * or KBu = sha1(home_cookie), if MN deregisters
++       */
++      sha1_init(&c);
++      sha1_compute(&c, hoc, MIPV6_RR_COOKIE_LENGTH);
++      if (coc)
++              sha1_compute(&c, coc, MIPV6_RR_COOKIE_LENGTH);
++      sha1_final(&c, key_bu);
++      DEBUG(DBG_RR, "Home and Care-of cookies used for calculating key ");
++      debug_print_buffer(DBG_RR, hoc,  MIPV6_RR_COOKIE_LENGTH);
++      if (coc)        
++              debug_print_buffer(DBG_RR, coc,  MIPV6_RR_COOKIE_LENGTH);
++
++      return key_bu;
++}
++
++void mipv6_rr_init(void)
++{
++      get_random_bytes(k_CN, HMAC_SHA1_KEY_SIZE);
++      memset(nonce_table, 0, MAX_NONCES * sizeof(struct nonce_timestamp));
++}
++
++#ifdef TEST_MIPV6_RR_CRYPTO
++void mipv6_test_rr(void)
++{
++      struct mipv6_rr_nonce *nonce;
++      struct in6_addr a1, a2;
++      int ind1, ind2;
++      u8 *ckie1 = NULL, *ckie2 = NULL;
++      u8 *key_mn = NULL, *key_cn = NULL;
++      mipv6_init_rr();
++
++      nonce = mipv6_rr_get_new_nonce();
++      if (!nonce) {
++              printk("mipv6_rr_get_new_nonce() failed, at 1! \n");
++              return;
++      }
++      mipv6_rr_cookie_create(&a1, &ckie1, nonce->index);
++      ind1 = nonce->index;
++      kfree(nonce);
++
++      nonce = mipv6_rr_get_new_nonce();
++      if (!nonce) {
++              printk("mipv6_rr_get_new_nonce() failed, at 2! \n");
++              return;
++      }
++
++      mipv6_rr_cookie_create(&a2, &ckie2, nonce->index); 
++      ind2 = nonce->index;
++      key_mn =  mipv6_rr_key_calc(ckie1, ckie2);
++
++      /* Create home and coa cookies based on indices */
++      mipv6_rr_cookie_create(&a1, &ckie1, ind1);
++      mipv6_rr_cookie_create(&a2, &ckie2, ind2);
++      key_cn =  mipv6_rr_key_calc(ckie1, ckie2);             
++      if (!key_cn || !key_mn) {
++              printk("creation of secret key failed!\n");
++              return;
++      }
++      if(memcmp(key_cn, key_mn, HMAC_SHA1_KEY_SIZE))
++              printk("mipv6_rr_key_calc produced different keys for MN and CN \n");
++      else
++              printk("mipv6_rr_crypto test OK\n");
++      kfree(nonce);
++      kfree(key_cn);
++      kfree(key_mn);
++}
++#endif
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/rr_crypto.h linux-2.4.25/net/ipv6/mobile_ip6/rr_crypto.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/rr_crypto.h   1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/rr_crypto.h       2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,72 @@
++/*
++ *      MIPL Mobile IPv6 Return routability crypto prototypes
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _RR_CRYPTO
++#define _RR_CRYPTO
++
++#include <linux/in6.h>
++
++/* Macros and data structures */
++
++#define MIPV6_RR_NONCE_LIFETIME 60 
++#define MIPV6_RR_NONCE_DATA_LENGTH 8
++#define MIPV6_RR_COOKIE_LENGTH 8
++#define COOKIE_SIZE 8
++#define MAX_NONCES 4
++#define HMAC_SHA1_KEY_SIZE 20
++ 
++struct mipv6_rr_nonce {
++      u_int16_t index;
++      u_int8_t data[MIPV6_RR_NONCE_DATA_LENGTH];
++};
++
++struct nonce_timestamp {
++      struct  mipv6_rr_nonce nonce;
++      unsigned long timestamp;
++      u_int8_t invalid; 
++};
++
++/* Function definitions */
++
++/* Return 1 if equal, 0 if not */
++static __inline__ int mipv6_equal_cookies(u8 *c1, u8 *c2)
++{
++      return (memcmp(c1, c2, MIPV6_RR_COOKIE_LENGTH) == 0);
++}
++
++/* Function declarations */
++
++/* Create cookie for HoTi and CoTi */
++extern void mipv6_rr_mn_cookie_create(u8 *cookie);
++
++/* Create cookie for HoT and CoT */
++extern int mipv6_rr_cookie_create(struct in6_addr *addr, u8 **ckie, u16 nonce_index);
++
++/* Calculate return routability key from home and care-of cookies, key length is 
++ *  HMAC_SHA1_KEY_SIZE  
++ */
++extern u_int8_t *mipv6_rr_key_calc(u8 *hoc, u8 *coc);
++
++extern struct mipv6_rr_nonce *mipv6_rr_get_new_nonce(void);
++
++/* For avoiding replay attacks when MN deregisters */
++extern void mipv6_rr_invalidate_nonce(u16 nonce_index);
++/*
++ * initializes the return routability crypto
++ */
++
++void mipv6_rr_init(void);
++
++#ifdef TEST_MIPV6_RR_CRYPTO
++void mipv6_test_rr(void);
++#endif /* TEST_MIPV6_RR_CRYPTO */
++
++#endif /* RR_CRYPTO */
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/sortedlist.c linux-2.4.25/net/ipv6/mobile_ip6/sortedlist.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/sortedlist.c  1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/sortedlist.c      2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,349 @@
++/**
++ * Sorted list - linked list with sortkey.
++ *
++ * Authors:
++ * Jaakko Laine <medved@iki.fi>
++ *
++ * $Id$
++ *
++ * 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 (at your option) any later version.
++ */
++
++#include <linux/kernel.h>
++#include <linux/types.h>
++#include <linux/list.h>
++#include <linux/slab.h>
++#include <linux/spinlock.h>
++#include <linux/string.h>
++
++struct mipv6_sorted_list_entry {
++      struct list_head list;
++      void *data;
++      int datalen;
++      unsigned long sortkey;
++};
++
++/**
++ * compare - compares two arbitrary data items
++ * @data1: first data item
++ * @data2: second data item
++ * @datalen: length of data items in bits
++ *
++ * datalen is in bits!
++ */
++int mipv6_bitwise_compare(const void *data1, const void *data2, int datalen)
++{
++      int n = datalen;
++      __u8 * ptr1 = (__u8 *)data1;
++      __u8 * ptr2 = (__u8 *)data2;
++      
++      for (; n>=0; n-=8, ptr1++, ptr2++) {
++              if (n >= 8) {
++                      if (*ptr1 != *ptr2)
++                              return 0;
++              } else {
++                      if ((*ptr1 ^ *ptr2) & ((~0) << (8 - n)))
++                              return 0;
++              }
++      }
++
++      return 1;
++}
++
++/**
++ * mipv6_slist_add - add an entry to sorted list
++ * @head: list_head of the sorted list
++ * @data: item to store
++ * @datalen: length of data (in bytes)
++ * @key: sortkey of item
++ *
++ * Allocates memory for entry and data
++ */
++int mipv6_slist_add(struct list_head *head, void *data, int datalen,
++                  unsigned long sortkey)
++{
++      struct list_head *pos;
++      struct mipv6_sorted_list_entry *entry, *tmp, *next;
++
++      entry = kmalloc(sizeof(struct mipv6_sorted_list_entry), GFP_ATOMIC);
++
++      if (!entry)
++              return -1;
++
++      entry->data = kmalloc(datalen, GFP_ATOMIC);
++
++      if (!entry->data) {
++              kfree(entry);
++              return -1;
++      }
++
++      memcpy(entry->data, data, datalen);
++      entry->datalen = datalen;
++      entry->sortkey = sortkey;
++
++      if ((pos = head->next) == head) {
++              list_add(&entry->list, head);
++              return 0;
++      }
++
++      tmp = list_entry(pos, struct mipv6_sorted_list_entry, list);
++      if (entry->sortkey < tmp->sortkey) {
++              list_add(&entry->list, head);
++              return 0;
++      }
++
++      for (; pos != head; pos = pos->next) {
++              tmp = list_entry(pos, struct mipv6_sorted_list_entry, list);
++              if (pos->next == head) {
++                      list_add(&entry->list, &tmp->list);
++                      return 0;
++              }
++              next = list_entry(pos->next, struct mipv6_sorted_list_entry, list);
++              if (entry->sortkey >= tmp->sortkey && entry->sortkey < next->sortkey) {
++                      list_add(&entry->list, &tmp->list);
++                      return 0;
++              }
++      }
++
++      /* never reached */
++      return -1;
++}
++
++/**
++ * mipv6_slist_get_first - get the first data item in the list
++ * @head: list_head of the sorted list
++ *
++ * Returns the actual data item, not copy, so don't kfree it
++ */
++void *mipv6_slist_get_first(struct list_head *head)
++{
++      struct mipv6_sorted_list_entry *entry;
++
++      if (list_empty(head))
++              return NULL;
++
++      entry = list_entry(head->next, struct mipv6_sorted_list_entry, list);
++      return entry->data;
++}
++
++/**
++ * mipv6_slist_del_first - delete (and get) the first item in list
++ * @head: list_head of the sorted list
++ *
++ * Remember to kfree the item
++ */
++void *mipv6_slist_del_first(struct list_head *head)
++{
++      void *tmp;
++      struct mipv6_sorted_list_entry *entry;
++
++      if (list_empty(head))
++              return NULL;
++
++      entry = list_entry(head->next, struct mipv6_sorted_list_entry, list);
++      tmp = entry->data;
++
++      list_del(head->next);
++      kfree(entry);
++
++      return tmp;
++}
++
++/**
++ * mipv6_slist_del_item - delete entry
++ * @head: list_head of the sorted list
++ * @data: item to delete
++ * @compare: function used for comparing the data items
++ *
++ * compare function needs to have prototype
++ * int (*compare)(const void *data1, const void *data2, int datalen)
++ */
++int mipv6_slist_del_item(struct list_head *head, void *data,
++                       int (*compare)(const void *data1, const void *data2,
++                                      int datalen))
++{
++      struct list_head *pos;
++      struct mipv6_sorted_list_entry *entry;
++
++      for(pos = head->next; pos != head; pos = pos->next) {
++              entry = list_entry(pos, struct mipv6_sorted_list_entry, list);
++              if (compare(data, entry->data, entry->datalen)) {
++                      list_del(pos);
++                      kfree(entry->data);
++                      kfree(entry);
++                      return 0;
++              }
++      }
++
++      return -1;
++}
++
++/**
++ * mipv6_slist_get_first_key - get sortkey of the first item
++ * @head: list_head of the sorted list
++ */
++unsigned long mipv6_slist_get_first_key(struct list_head *head)
++{
++      struct mipv6_sorted_list_entry *entry;
++
++      if (list_empty(head))
++              return 0;
++
++      entry = list_entry(head->next, struct mipv6_sorted_list_entry, list);
++      return entry->sortkey;
++}
++
++/**
++ * mipv6_slist_get_key - get sortkey of the data item
++ * @head: list_head of the sorted list
++ * @data: the item to search for
++ * @compare: function used for comparing the data items
++ *
++ * compare function needs to have prototype
++ * int (*compare)(const void *data1, const void *data2, int datalen)
++ */
++unsigned long mipv6_slist_get_key(struct list_head *head, void *data,
++                                int (*compare)(const void *data1,
++                                               const void *data2,
++                                               int datalen))
++{
++      struct list_head *pos;
++      struct mipv6_sorted_list_entry *entry;
++      
++      for(pos = head->next; pos != head; pos = pos->next) {
++              entry = list_entry(pos, struct mipv6_sorted_list_entry, list);
++              if (compare(data, entry->data, entry->datalen))
++                      return entry->sortkey;
++      }
++      
++      return 0;
++}
++
++/**
++ * mipv6_slist_get_data - get the data item identified by sortkey
++ * @head: list_head of the sorted list
++ * @key: sortkey of the item
++ *
++ * Returns the actual data item, not copy, so don't kfree it
++ */
++void *mipv6_slist_get_data(struct list_head *head, unsigned long sortkey)
++{
++      struct list_head *pos;
++      struct mipv6_sorted_list_entry *entry;
++
++      list_for_each(pos, head) {
++              entry = list_entry(pos, struct mipv6_sorted_list_entry, list);
++              if (entry->sortkey == sortkey) 
++                      return entry->data;
++      }
++
++      return NULL;
++}
++
++/**
++ * reorder_entry - move an entry to a new position according to sortkey
++ * @head: list_head of the sorted list
++ * @entry_pos: current place of the entry
++ * @key: new sortkey
++ */
++static void reorder_entry(struct list_head *head, struct list_head *entry_pos,
++                        unsigned long sortkey)
++{
++      struct list_head *pos;
++      struct mipv6_sorted_list_entry *entry;
++
++      list_del(entry_pos);
++
++      for (pos = head->next; pos != head; pos = pos->next) {
++              entry = list_entry(pos, struct mipv6_sorted_list_entry, list);
++              if (sortkey >= entry->sortkey) {
++                      list_add(entry_pos, &entry->list);
++                      return;
++              }
++      }
++
++      list_add(entry_pos, head);
++}
++
++/**
++ * mipv6_slist_modify - modify data item
++ * @head: list_head of the sorted list
++ * @data: item, whose sortkey is to be modified
++ * @datalen: datalen in bytes
++ * @new_key: new sortkey
++ * @compare: function used for comparing the data items
++ *
++ * Compies the new data on top of the old one, if compare function returns
++ * true. If there's no matching entry, new one will be created.
++ * Compare function needs to have prototype
++ * int (*compare)(const void *data1, const void *data2, int datalen)
++ */
++int mipv6_slist_modify(struct list_head *head, void *data, int datalen,
++                     unsigned long new_key,
++                     int (*compare)(const void *data1, const void *data2,
++                                    int datalen))
++{
++      struct list_head *pos;
++      struct mipv6_sorted_list_entry *entry;
++
++      for (pos = head->next; pos != head; pos = pos->next) {
++              entry = list_entry(pos, struct mipv6_sorted_list_entry, list);
++              if (compare(data, entry->data, datalen)) {
++                      memcpy(entry->data, data, datalen);
++                      entry->sortkey = new_key;
++                      reorder_entry(head, &entry->list, new_key);
++                      return 0;
++              }
++      }
++
++      return mipv6_slist_add(head, data, datalen, new_key);
++}
++
++/**
++ * mipv6_slist_push_first - move the first entry to place indicated by new_key
++ * @head: list_head of the sorted list
++ * @new_key: new sortkey
++ */
++int mipv6_slist_push_first(struct list_head *head, unsigned long new_key)
++{
++      struct mipv6_sorted_list_entry *entry;
++
++      if (list_empty(head))
++              return -1;
++
++      entry = list_entry(head->next, struct mipv6_sorted_list_entry, list);
++      entry->sortkey = new_key;
++
++      reorder_entry(head, head->next, new_key);
++      return 0;
++}
++
++/**
++ * mipv6_slist_for_each - apply func to every item in list
++ * @head: list_head of the sorted list
++ * @args: args to pass to func
++ * @func: function to use
++ *
++ * function must be of type
++ * int (*func)(void *data, void *args, unsigned long sortkey)
++ * List iteration will stop once func has been applied to every item
++ * or when func returns true
++ */
++int mipv6_slist_for_each(struct list_head *head, void *args,
++                       int (*func)(void *data, void *args,
++                                   unsigned long sortkey))
++{
++      struct list_head *pos;
++      struct mipv6_sorted_list_entry *entry;
++
++      list_for_each(pos, head) {
++              entry = list_entry(pos, struct mipv6_sorted_list_entry, list);
++              if (func(entry->data, args, entry->sortkey))
++                      break;
++      }
++
++      return 0;
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/sortedlist.h linux-2.4.25/net/ipv6/mobile_ip6/sortedlist.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/sortedlist.h  1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/sortedlist.h      2004-06-26 11:29:31.000000000 +0100
+@@ -0,0 +1,133 @@
++/*
++ *      Sorted list - linked list with sortkey
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++/**
++ * compare - compares two arbitrary data items
++ * @data1: first data item
++ * @data2: second data item
++ * @datalen: length of data items in bits
++ *
++ * datalen is in bits!
++ */
++int mipv6_bitwise_compare(const void *data1, const void *data2, int datalen);
++
++/**
++ * mipv6_slist_add - add an entry to sorted list
++ * @head: list_head of the sorted list
++ * @data: item to store
++ * @datalen: length of data (in bytes)
++ * @key: sortkey of item
++ *
++ * Allocates memory for entry and data
++ */
++int mipv6_slist_add(struct list_head *head, void *data, int datalen,
++                  unsigned long sortkey);
++
++/**
++ * mipv6_slist_get_first - get the first data item in the list
++ * @head: list_head of the sorted list
++ *
++ * Returns the actual data item, not copy, so don't kfree it
++ */
++void *mipv6_slist_get_first(struct list_head *head);
++
++/**
++ * mipv6_slist_del_first - delete (and get) the first item in list
++ * @head: list_head of the sorted list
++ *
++ * Remember to kfree the item
++ */
++void *mipv6_slist_del_first(struct list_head *head);
++
++/**
++ * mipv6_slist_del_item - delete entry
++ * @head: list_head of the sorted list
++ * @data: item to delete
++ * @compare: function used for comparing the data items
++ *
++ * compare function needs to have prototype
++ * int (*compare)(const void *data1, const void *data2, int datalen) where
++ * datalen is in bits
++ */
++int mipv6_slist_del_item(struct list_head *head, void *data,
++                       int (*compare)(const void *data1, const void *data2,
++                                      int datalen));
++
++/**
++ * mipv6_slist_get_first_key - get sortkey of the first item
++ * @head: list_head of the sorted list
++ */
++unsigned long mipv6_slist_get_first_key(struct list_head *head);
++
++/**
++ * mipv6_slist_get_key - get sortkey of the data item
++ * @head: list_head of the sorted list
++ * @data: the item to search for
++ * @compare: function used for comparing the data items
++ *
++ * compare function needs to have prototype
++ * int (*compare)(const void *data1, const void *data2, int datalen) where
++ * datalen is in bits
++ */
++unsigned long mipv6_slist_get_key(struct list_head *head, void *data,
++                                int (*compare)(const void *data1,
++                                               const void *data2,
++                                               int datalen));
++
++/**
++ * mipv6_slist_get_data - get the data item identified by sortkey
++ * @head: list_head of the sorted list
++ * @key: sortkey of the item
++ *
++ * Returns the actual data item, not copy, so don't kfree it
++ */
++void *mipv6_slist_get_data(struct list_head *head, unsigned long sortkey);
++
++/**
++ * mipv6_slist_modify - modify data item
++ * @head: list_head of the sorted list
++ * @data: item, whose sortkey is to be modified
++ * @datalen: datalen in bytes
++ * @new_key: new sortkey
++ * @compare: function used for comparing the data items
++ *
++ * Compies the new data on top of the old one, if compare function returns
++ * non-negative. If there's no matching entry, new one will be created.
++ * Compare function needs to have prototype
++ * int (*compare)(const void *data1, const void *data2, int datalen) where
++ * datalen is in bits.
++ */
++int mipv6_slist_modify(struct list_head *head, void *data, int datalen,
++                     unsigned long new_key,
++                     int (*compare)(const void *data1, const void *data2,
++                                    int datalen));
++
++/**
++ * mipv6_slist_push_first - move the first entry to place indicated by new_key
++ * @head: list_head of the sorted list
++ * @new_key: new sortkey
++ */
++int mipv6_slist_push_first(struct list_head *head, unsigned long new_key);
++
++/**
++ * mipv6_slist_for_each - apply func to every item in list
++ * @head: list_head of the sorted list
++ * @args: args to pass to func
++ * @func: function to use
++ *
++ * function must be of type
++ * int (*func)(void *data, void *args, unsigned long sortkey)
++ * List iteration will stop once func has been applied to every item
++ * or when func returns true
++ */
++int mipv6_slist_for_each(struct list_head *head, void *args,
++                       int (*func)(void *data, void *args,
++                                   unsigned long sortkey));
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/stats.c linux-2.4.25/net/ipv6/mobile_ip6/stats.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/stats.c       1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/stats.c   2004-06-26 11:29:32.000000000 +0100
+@@ -0,0 +1,90 @@
++/*
++ *      Statistics module
++ *
++ *      Authors:
++ *      Sami Kivisaari          <skivisaa@cc.hut.fi>
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ *     
++ *      Changes:
++ *      Krishna Kumar, 
++ *      Venkata Jagana   :  SMP locking fix  
++ */
++
++#include <linux/config.h>
++#include <linux/proc_fs.h>
++#include "stats.h"
++
++struct mipv6_statistics mipv6_stats;
++
++static int proc_info_dump(
++      char *buffer, char **start,
++      off_t offset, int length)
++{
++      struct inf {
++              char *name;
++              int *value;
++      } int_stats[] = {
++              {"NEncapsulations", &mipv6_stats.n_encapsulations},
++              {"NDecapsulations", &mipv6_stats.n_decapsulations},
++              {"NBindRefreshRqsRcvd", &mipv6_stats.n_brr_rcvd},
++              {"NHomeTestInitsRcvd", &mipv6_stats.n_hoti_rcvd},
++              {"NCareofTestInitsRcvd", &mipv6_stats.n_coti_rcvd},
++              {"NHomeTestRcvd", &mipv6_stats.n_hot_rcvd},
++              {"NCareofTestRcvd", &mipv6_stats.n_cot_rcvd},
++              {"NBindUpdatesRcvd", &mipv6_stats.n_bu_rcvd},
++              {"NBindAcksRcvd", &mipv6_stats.n_ba_rcvd},
++              {"NBindNAcksRcvd", &mipv6_stats.n_ban_rcvd},
++              {"NBindErrorsRcvd", &mipv6_stats.n_be_rcvd},
++              {"NBindRefreshRqsSent", &mipv6_stats.n_brr_sent},
++              {"NHomeTestInitsSent", &mipv6_stats.n_hoti_sent},
++              {"NCareofTestInitsSent", &mipv6_stats.n_coti_sent},
++              {"NHomeTestSent", &mipv6_stats.n_hot_sent},
++              {"NCareofTestSent", &mipv6_stats.n_cot_sent},
++              {"NBindUpdatesSent", &mipv6_stats.n_bu_sent},
++              {"NBindAcksSent", &mipv6_stats.n_ba_sent},
++              {"NBindNAcksSent", &mipv6_stats.n_ban_sent},
++              {"NBindErrorsSent", &mipv6_stats.n_be_sent},
++              {"NBindUpdatesDropAuth", &mipv6_stats.n_bu_drop.auth},
++              {"NBindUpdatesDropInvalid", &mipv6_stats.n_bu_drop.invalid},
++              {"NBindUpdatesDropMisc", &mipv6_stats.n_bu_drop.misc},
++              {"NBindAcksDropAuth", &mipv6_stats.n_bu_drop.auth},
++              {"NBindAcksDropInvalid", &mipv6_stats.n_bu_drop.invalid},
++              {"NBindAcksDropMisc", &mipv6_stats.n_bu_drop.misc},
++              {"NBindRqsDropAuth", &mipv6_stats.n_bu_drop.auth},
++              {"NBindRqsDropInvalid", &mipv6_stats.n_bu_drop.invalid},
++              {"NBindRqsDropMisc", &mipv6_stats.n_bu_drop.misc}
++      };
++
++      int i;
++      int len = 0;
++      for(i=0; i<sizeof(int_stats) / sizeof(struct inf); i++) {
++              len += sprintf(buffer + len, "%s = %d\n",
++                             int_stats[i].name, *int_stats[i].value);
++      }
++
++      *start = buffer + offset;
++
++      len -= offset;
++
++      if(len > length) len = length;
++
++      return len;
++}
++
++int mipv6_stats_init(void)
++{
++      memset(&mipv6_stats, 0, sizeof(struct mipv6_statistics));
++      proc_net_create("mip6_stat", 0, proc_info_dump);
++      return 0;
++}
++
++void mipv6_stats_exit(void)
++{
++      proc_net_remove("mip6_stat");
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/stats.h linux-2.4.25/net/ipv6/mobile_ip6/stats.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/stats.h       1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/stats.h   2004-06-26 11:29:32.000000000 +0100
+@@ -0,0 +1,71 @@
++/*
++ *      MIPL Mobile IPv6 Statistics header file
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _STATS_H
++#define _STATS_H
++
++struct mipv6_drop {
++      __u32 auth;
++      __u32 invalid;
++      __u32 misc;
++};
++
++struct mipv6_statistics {
++      int n_encapsulations;
++      int n_decapsulations;
++      int n_mh_in_msg;
++      int n_mh_in_error;
++      int n_mh_out_msg;
++      int n_mh_out_error;
++
++      int n_brr_rcvd;
++      int n_hoti_rcvd;
++      int n_coti_rcvd;
++      int n_hot_rcvd;
++      int n_cot_rcvd;
++      int n_bu_rcvd;
++      int n_ba_rcvd;
++      int n_ban_rcvd;
++      int n_be_rcvd;
++
++      int n_brr_sent;
++      int n_hoti_sent;
++      int n_coti_sent;
++      int n_hot_sent;
++      int n_cot_sent;
++      int n_bu_sent;
++      int n_ba_sent;
++      int n_ban_sent;
++      int n_be_sent;
++
++      int n_ha_rcvd;
++      int n_ha_sent;
++
++      struct mipv6_drop n_bu_drop;
++      struct mipv6_drop n_ba_drop;
++      struct mipv6_drop n_brr_drop;
++      struct mipv6_drop n_be_drop;
++      struct mipv6_drop n_ha_drop;
++};
++
++extern struct mipv6_statistics mipv6_stats;
++
++#ifdef CONFIG_SMP
++/* atomic_t is max 24 bits long */
++#define MIPV6_INC_STATS(X) atomic_inc((atomic_t *)&mipv6_stats.X);
++#else
++#define MIPV6_INC_STATS(X) mipv6_stats.X++;
++#endif
++
++int mipv6_stats_init(void);
++void mipv6_stats_exit(void);
++
++#endif
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel.h linux-2.4.25/net/ipv6/mobile_ip6/tunnel.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel.h      1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/tunnel.h  2004-06-26 11:29:32.000000000 +0100
+@@ -0,0 +1,35 @@
++/*
++ *      MIPL Mobile IPv6 IP6-IP6 tunneling header file
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _TUNNEL_H
++#define _TUNNEL_H
++
++#include <linux/in6.h>
++#include <linux/if_arp.h>
++#include <net/ipv6_tunnel.h>
++
++static __inline__ int is_mip6_tnl(struct ip6_tnl *t)
++{
++      return (t != NULL && 
++              t->parms.flags & IP6_TNL_F_KERNEL_DEV &&
++              t->parms.flags & IP6_TNL_F_MIP6_DEV);
++                      
++}
++
++static __inline__ int dev_is_mip6_tnl(struct net_device *dev)
++{
++      struct ip6_tnl *t = (struct ip6_tnl *)dev->priv;
++      return (dev->type == ARPHRD_TUNNEL6 && is_mip6_tnl(t));
++}
++
++
++#endif
++
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_ha.c linux-2.4.25/net/ipv6/mobile_ip6/tunnel_ha.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_ha.c   1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/tunnel_ha.c       2004-06-26 11:29:32.000000000 +0100
+@@ -0,0 +1,264 @@
++/*
++ *    IPv6-IPv6 tunneling module
++ *
++ *    Authors:
++ *    Sami Kivisaari          <skivisaa@cc.hut.fi>
++ *    Ville Nuorvala          <vnuorval@tml.hut.fi>
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ *
++ */
++
++#include <linux/net.h>
++#include <linux/skbuff.h>
++#include <linux/ipv6.h>
++#include <linux/net.h>
++#include <linux/netdevice.h>
++#include <linux/init.h>
++#include <linux/route.h>
++#include <linux/ipv6_route.h>
++
++#ifdef CONFIG_SYSCTL
++#include <linux/sysctl.h>
++#endif /* CONFIG_SYSCTL */
++
++#include <net/protocol.h>
++#include <net/ipv6.h>
++#include <net/ip6_route.h>
++#include <net/dst.h>
++#include <net/addrconf.h>
++
++#include "tunnel.h"
++#include "debug.h"
++#include "stats.h"
++#include "config.h"
++
++#define MIPV6_TNL_MAX IP6_TNL_MAX
++#define MIPV6_TNL_MIN 1
++
++int mipv6_max_tnls = 3;
++int mipv6_min_tnls = 1;
++
++DECLARE_MUTEX(tnl_sem);
++
++int mipv6_max_tnls_sysctl(ctl_table *ctl, int write, struct file *filp,
++                        void *buffer, size_t *lenp)
++{
++      int err;
++      
++      DEBUG_FUNC();
++
++      down(&tnl_sem);
++      if (write) {
++              int diff;
++              int old_max_tnls = mipv6_max_tnls;
++              err = proc_dointvec(ctl, write, filp, buffer, lenp);
++              if (err < 0) 
++                      goto out;
++              if (mipv6_max_tnls < mipv6_min_tnls || 
++                  mipv6_max_tnls > MIPV6_TNL_MAX) {
++                      mipv6_max_tnls = old_max_tnls;
++                      goto out;
++              }
++              if (mipv6_max_tnls < old_max_tnls) {
++                      diff = old_max_tnls - mipv6_max_tnls;
++                      ip6ip6_tnl_dec_max_kdev_count(diff);
++              } else if (mipv6_max_tnls > old_max_tnls) {
++                      diff = mipv6_max_tnls - old_max_tnls;
++                      ip6ip6_tnl_inc_max_kdev_count(diff);
++              }
++      } else {
++              err = proc_dointvec(ctl, write, filp, buffer, lenp);
++      }
++out:
++      up(&tnl_sem);
++      return err;
++}
++
++int mipv6_min_tnls_sysctl(ctl_table *ctl, int write, struct file *filp,
++                        void *buffer, size_t *lenp)
++{
++      int err;
++
++      DEBUG_FUNC();
++
++      down(&tnl_sem);
++      if (write) {
++              int diff;
++              int old_min_tnls = mipv6_min_tnls;
++              err = proc_dointvec(ctl, write, filp, buffer, lenp);
++              if (err < 0) 
++                      goto out;
++              if (mipv6_min_tnls > mipv6_max_tnls || 
++                  mipv6_min_tnls < MIPV6_TNL_MIN) {
++                      mipv6_min_tnls = old_min_tnls;
++                      goto out;
++              }
++              if (mipv6_min_tnls < old_min_tnls) {
++                      diff = old_min_tnls - mipv6_min_tnls;
++                      ip6ip6_tnl_dec_min_kdev_count(diff);
++              } else if (mipv6_min_tnls > old_min_tnls) {
++                      diff = mipv6_min_tnls - old_min_tnls;
++                      ip6ip6_tnl_inc_min_kdev_count(diff);
++              }
++      } else {
++              err = proc_dointvec(ctl, write, filp, buffer, lenp);
++      }
++out:
++      up(&tnl_sem);
++      return err;
++}
++
++static __inline__ int mipv6_tnl_add(struct in6_addr *remote, 
++                                  struct in6_addr *local) 
++{
++      struct ip6_tnl_parm p;
++      int ret;
++
++      DEBUG_FUNC();
++
++      memset(&p, 0, sizeof(p));
++      p.proto = IPPROTO_IPV6;
++      ipv6_addr_copy(&p.laddr, local);
++      ipv6_addr_copy(&p.raddr, remote);
++      p.hop_limit = 255;
++      p.flags = (IP6_TNL_F_KERNEL_DEV | IP6_TNL_F_MIP6_DEV |
++                 IP6_TNL_F_IGN_ENCAP_LIMIT);
++
++      ret = ip6ip6_kernel_tnl_add(&p);
++      if (ret > 0) {
++              DEBUG(DBG_INFO, "added tunnel from: "
++                    "%x:%x:%x:%x:%x:%x:%x:%x to: %x:%x:%x:%x:%x:%x:%x:%x", 
++                    NIPV6ADDR(local), NIPV6ADDR(remote));
++      } else {
++              DEBUG(DBG_WARNING, "unable to add tunnel from: "
++                    "%x:%x:%x:%x:%x:%x:%x:%x to: %x:%x:%x:%x:%x:%x:%x:%x", 
++                    NIPV6ADDR(local), NIPV6ADDR(remote));             
++      }
++      return ret;
++}
++
++static __inline__ int mipv6_tnl_del(struct in6_addr *remote, 
++                                  struct in6_addr *local) 
++{
++      struct ip6_tnl *t = ip6ip6_tnl_lookup(remote, local);
++      
++      DEBUG_FUNC();
++      
++      if (t != NULL && (t->parms.flags & IP6_TNL_F_MIP6_DEV)) {
++              DEBUG(DBG_INFO, "deleting tunnel from: "
++                    "%x:%x:%x:%x:%x:%x:%x:%x to: %x:%x:%x:%x:%x:%x:%x:%x", 
++                    NIPV6ADDR(local), NIPV6ADDR(remote));
++
++              return ip6ip6_kernel_tnl_del(t);
++      }
++      return 0;
++}
++
++static int add_route_to_mn(struct in6_addr *coa, struct in6_addr *ha_addr, 
++                         struct in6_addr *home_addr) 
++{
++      struct in6_rtmsg rtmsg;
++      int err;
++      struct ip6_tnl *t = ip6ip6_tnl_lookup(coa, ha_addr);
++      
++      if (!is_mip6_tnl(t)) {
++              DEBUG(DBG_CRITICAL,"Tunnel missing");
++              return -ENODEV;
++      }
++      
++      DEBUG(DBG_INFO, "adding route to: %x:%x:%x:%x:%x:%x:%x:%x via "
++            "tunnel device", NIPV6ADDR(home_addr));
++
++      memset(&rtmsg, 0, sizeof(rtmsg));
++      ipv6_addr_copy(&rtmsg.rtmsg_dst, home_addr);
++      rtmsg.rtmsg_dst_len = 128;
++      rtmsg.rtmsg_type = RTMSG_NEWROUTE;
++      rtmsg.rtmsg_flags = RTF_UP | RTF_NONEXTHOP | RTF_HOST | RTF_MOBILENODE;
++      rtmsg.rtmsg_ifindex = t->dev->ifindex;
++      rtmsg.rtmsg_metric = IP6_RT_PRIO_MIPV6;
++      if ((err = ip6_route_add(&rtmsg, NULL)) == -EEXIST) {
++              err = 0;
++      }
++      return err;
++}
++
++static void del_route_to_mn(struct in6_addr *coa, struct in6_addr *ha_addr, 
++                          struct in6_addr *home_addr) 
++{
++      struct ip6_tnl *t = ip6ip6_tnl_lookup(coa, ha_addr);
++
++      DEBUG_FUNC();
++
++      if (is_mip6_tnl(t)) {
++              struct in6_rtmsg rtmsg;
++
++              DEBUG(DBG_INFO, "deleting route to: %x:%x:%x:%x:%x:%x:%x:%x "
++                    " via tunnel device", NIPV6ADDR(home_addr));
++
++              memset(&rtmsg, 0, sizeof(rtmsg));
++              ipv6_addr_copy(&rtmsg.rtmsg_dst, home_addr);
++              rtmsg.rtmsg_dst_len = 128;
++              rtmsg.rtmsg_ifindex = t->dev->ifindex;
++              rtmsg.rtmsg_metric = IP6_RT_PRIO_MIPV6;
++              ip6_route_del(&rtmsg, NULL);
++      }
++}
++
++
++int mipv6_add_tnl_to_mn(struct in6_addr *coa, 
++                      struct in6_addr *ha_addr,
++                      struct in6_addr *home_addr)
++{
++      int ret;
++
++      DEBUG_FUNC();
++
++      ret = mipv6_tnl_add(coa, ha_addr);
++
++      if (ret > 0) {
++              int err = add_route_to_mn(coa, ha_addr, home_addr);
++              if (err) {
++                      if (err != -ENODEV) {
++                              mipv6_tnl_del(coa, ha_addr);
++                      }
++                      return err;
++              }
++      }
++      return ret;
++} 
++
++int mipv6_del_tnl_to_mn(struct in6_addr *coa, 
++                      struct in6_addr *ha_addr,
++                      struct in6_addr *home_addr)
++{
++      DEBUG_FUNC();
++      del_route_to_mn(coa, ha_addr, home_addr);
++      return mipv6_tnl_del(coa, ha_addr);
++} 
++
++__init void mipv6_initialize_tunnel(void)
++{
++      down(&tnl_sem);
++      ip6ip6_tnl_inc_max_kdev_count(mipv6_max_tnls);
++      ip6ip6_tnl_inc_min_kdev_count(mipv6_min_tnls);
++      up(&tnl_sem);
++      mip6_fn.bce_tnl_rt_add = add_route_to_mn;
++      mip6_fn.bce_tnl_rt_del = del_route_to_mn;
++}
++
++__exit void mipv6_shutdown_tunnel(void)
++{
++      mip6_fn.bce_tnl_rt_del = NULL;
++      mip6_fn.bce_tnl_rt_add = NULL;
++      down(&tnl_sem);
++      ip6ip6_tnl_dec_min_kdev_count(mipv6_min_tnls);
++      ip6ip6_tnl_dec_max_kdev_count(mipv6_max_tnls);
++      up(&tnl_sem);
++}
++
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_ha.h linux-2.4.25/net/ipv6/mobile_ip6/tunnel_ha.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_ha.h   1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/tunnel_ha.h       2004-06-26 11:29:32.000000000 +0100
+@@ -0,0 +1,20 @@
++#ifndef _TUNNEL_HA_H
++#define _TUNNEL_HA_H
++
++#include "tunnel.h"
++
++extern int mipv6_max_tnls;
++extern int mipv6_min_tnls;
++
++extern void mipv6_initialize_tunnel(void);
++extern void mipv6_shutdown_tunnel(void);
++
++extern int mipv6_add_tnl_to_mn(struct in6_addr *coa, 
++                             struct in6_addr *ha_addr,
++                             struct in6_addr *home_addr);
++
++extern int mipv6_del_tnl_to_mn(struct in6_addr *coa, 
++                             struct in6_addr *ha_addr,
++                             struct in6_addr *home_addr);
++
++#endif
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_mn.c linux-2.4.25/net/ipv6/mobile_ip6/tunnel_mn.c
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_mn.c   1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/tunnel_mn.c       2004-06-26 11:29:32.000000000 +0100
+@@ -0,0 +1,160 @@
++/*
++ *    IPv6-IPv6 tunneling module
++ *
++ *    Authors:
++ *    Sami Kivisaari          <skivisaa@cc.hut.fi>
++ *    Ville Nuorvala          <vnuorval@tml.hut.fi>
++ *
++ *    $Id$
++ *
++ *    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 (at your option) any later version.
++ *
++ */
++
++#include <linux/net.h>
++#include <linux/skbuff.h>
++#include <linux/ipv6.h>
++#include <linux/net.h>
++#include <linux/netdevice.h>
++#include <linux/init.h>
++#include <linux/route.h>
++#include <linux/ipv6_route.h>
++
++#ifdef CONFIG_SYSCTL
++#include <linux/sysctl.h>
++#endif /* CONFIG_SYSCTL */
++
++#include <net/protocol.h>
++#include <net/ipv6.h>
++#include <net/ip6_route.h>
++#include <net/dst.h>
++#include <net/addrconf.h>
++
++#include "tunnel.h"
++#include "debug.h"
++#include "stats.h"
++
++static struct net_device *mn_ha_tdev;
++
++static spinlock_t mn_ha_lock = SPIN_LOCK_UNLOCKED;
++
++static __inline__ int add_reverse_route(struct in6_addr *ha_addr,
++                                      struct in6_addr *home_addr, 
++                                      struct net_device *tdev) 
++{
++      struct in6_rtmsg rtmsg;
++      int err;
++
++      DEBUG_FUNC();
++
++      memset(&rtmsg, 0, sizeof(rtmsg));
++      rtmsg.rtmsg_type = RTMSG_NEWROUTE;
++      ipv6_addr_copy(&rtmsg.rtmsg_src, home_addr);
++      rtmsg.rtmsg_src_len = 128;
++      rtmsg.rtmsg_flags = RTF_UP | RTF_DEFAULT;
++      rtmsg.rtmsg_ifindex = tdev->ifindex;
++      rtmsg.rtmsg_metric = IP6_RT_PRIO_MIPV6;
++      if ((err = ip6_route_add(&rtmsg, NULL)) == -EEXIST) {
++              return 0;
++      }
++      return err;     
++}
++
++static __inline__ void del_reverse_route(struct in6_addr *ha_addr, 
++                                       struct in6_addr *home_addr,
++                                       struct net_device *tdev) 
++{
++      struct in6_rtmsg rtmsg;
++
++      DEBUG(DBG_INFO, "removing reverse route via tunnel device");
++
++      memset(&rtmsg, 0, sizeof(rtmsg));
++      ipv6_addr_copy(&rtmsg.rtmsg_src, home_addr);
++      rtmsg.rtmsg_src_len = 128;
++      rtmsg.rtmsg_ifindex = tdev->ifindex;
++      rtmsg.rtmsg_metric = IP6_RT_PRIO_MIPV6;
++      ip6_route_del(&rtmsg, NULL);
++}
++
++int mipv6_add_tnl_to_ha(void)
++{
++      struct ip6_tnl_parm p;
++      struct ip6_tnl *t;
++      int err;
++
++      DEBUG_FUNC();
++
++      memset(&p, 0, sizeof(p));
++      p.proto = IPPROTO_IPV6;
++      p.hop_limit = 255;
++      p.flags = (IP6_TNL_F_KERNEL_DEV | IP6_TNL_F_MIP6_DEV |
++                 IP6_TNL_F_IGN_ENCAP_LIMIT);
++      strcpy(p.name, "mip6mnha1");
++
++      rtnl_lock();
++      if ((err = ip6ip6_tnl_create(&p, &t))) {
++              rtnl_unlock();
++              return err;
++      }
++      spin_lock_bh(&mn_ha_lock);
++
++      if (!mn_ha_tdev) {
++              mn_ha_tdev = t->dev;
++              dev_hold(mn_ha_tdev);
++      }
++      spin_unlock_bh(&mn_ha_lock);
++      dev_open(t->dev);
++      rtnl_unlock();
++      return 0;
++} 
++
++int mipv6_mv_tnl_to_ha(struct in6_addr *ha_addr,
++                     struct in6_addr *coa,
++                     struct in6_addr *home_addr)
++{
++      int err = -ENODEV;
++
++      DEBUG_FUNC();
++
++      spin_lock_bh(&mn_ha_lock);
++      if (mn_ha_tdev) {
++              struct ip6_tnl_parm p;
++              memset(&p, 0, sizeof(p));
++              p.proto = IPPROTO_IPV6;
++              ipv6_addr_copy(&p.laddr, coa);
++              ipv6_addr_copy(&p.raddr, ha_addr);
++              p.hop_limit = 255;
++              p.flags = (IP6_TNL_F_KERNEL_DEV | IP6_TNL_F_MIP6_DEV |
++                         IP6_TNL_F_IGN_ENCAP_LIMIT);
++
++              ip6ip6_tnl_change((struct ip6_tnl *) mn_ha_tdev->priv, &p);
++              if (ipv6_addr_cmp(coa, home_addr)) {
++                      err = add_reverse_route(ha_addr, home_addr, 
++                                              mn_ha_tdev);
++              } else {
++                      del_reverse_route(ha_addr, home_addr, mn_ha_tdev);
++                      err = 0;
++              }
++      }
++      spin_unlock_bh(&mn_ha_lock);
++      return err;
++}
++
++void mipv6_del_tnl_to_ha(void)
++{
++      struct net_device *dev;
++
++      DEBUG_FUNC();
++
++      rtnl_lock();
++      spin_lock_bh(&mn_ha_lock);
++      dev = mn_ha_tdev;
++      mn_ha_tdev = NULL;
++      spin_unlock_bh(&mn_ha_lock);
++      dev_put(dev);
++      unregister_netdevice(dev);
++      rtnl_unlock();
++}
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_mn.h linux-2.4.25/net/ipv6/mobile_ip6/tunnel_mn.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/tunnel_mn.h   1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/tunnel_mn.h       2004-06-26 11:29:32.000000000 +0100
+@@ -0,0 +1,14 @@
++#ifndef _TUNNEL_MN_H
++#define _TUNNEL_MN_H
++
++#include "tunnel.h"
++
++extern int mipv6_add_tnl_to_ha(void);
++
++extern int mipv6_mv_tnl_to_ha(struct in6_addr *ha_addr, 
++                            struct in6_addr *coa,
++                            struct in6_addr *home_addr);
++
++extern int mipv6_del_tnl_to_ha(void);
++
++#endif
+diff -uprN linux-2.4.25.old/net/ipv6/mobile_ip6/util.h linux-2.4.25/net/ipv6/mobile_ip6/util.h
+--- linux-2.4.25.old/net/ipv6/mobile_ip6/util.h        1970-01-01 01:00:00.000000000 +0100
++++ linux-2.4.25/net/ipv6/mobile_ip6/util.h    2004-06-26 11:29:32.000000000 +0100
+@@ -0,0 +1,91 @@
++/*
++ *      MIPL Mobile IPv6 Utility functions
++ *
++ *      $Id$
++ *
++ *      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 (at your option) any later version.
++ */
++
++#ifndef _UTIL_H
++#define _UTIL_H
++
++#include <linux/in6.h>
++#include <asm/byteorder.h>
++
++/**
++ * mipv6_prefix_compare - Compare two IPv6 prefixes
++ * @addr: IPv6 address
++ * @prefix: IPv6 address
++ * @nprefix: number of bits to compare
++ *
++ * Perform prefix comparison bitwise for the @nprefix first bits
++ * Returns 1, if the prefixes are the same, 0 otherwise 
++ **/
++static inline int mipv6_prefix_compare(const struct in6_addr *addr,
++                                     const struct in6_addr *prefix, 
++                                     const unsigned int pfix_len)
++{
++      int i;
++      unsigned int nprefix = pfix_len;
++
++      if (nprefix > 128)
++              return 0;
++
++      for (i = 0; nprefix > 0; nprefix -= 32, i++) {
++              if (nprefix >= 32) {
++                      if (addr->s6_addr32[i] != prefix->s6_addr32[i])
++                              return 0;
++              } else {
++                      if (((addr->s6_addr32[i] ^ prefix->s6_addr32[i]) &
++                           ((~0) << (32 - nprefix))) != 0)
++                              return 0;
++                      return 1;
++              }
++      }
++
++      return 1;
++}
++
++/**
++ * homeagent_anycast - Compute Home Agent anycast address
++ * @ac_addr: append home agent anycast suffix to passed prefix
++ * @prefix: prefix ha anycast address is generated from
++ * @plen: length of prefix in bits
++ *
++ * Calculate corresponding Home Agent Anycast Address (RFC2526) in a
++ * given subnet.
++ */
++static inline int 
++mipv6_ha_anycast(struct in6_addr *ac_addr, struct in6_addr *prefix, int plen)
++{
++      if (plen <= 0 || plen > 120)  {
++              /* error, interface id should be minimum 8 bits */
++              return -1;
++      }
++      ipv6_addr_copy(ac_addr, prefix);
++
++      if (plen < 32)
++              ac_addr->s6_addr32[0] |= htonl((u32)(~0) >> plen);
++      if (plen < 64)
++              ac_addr->s6_addr32[1] |= htonl((u32)(~0) >> (plen > 32 ? plen % 32 : 0));
++      if (plen < 92)
++              ac_addr->s6_addr32[2] |= htonl((u32)(~0) >> (plen > 64 ? plen % 32 : 0));
++      if (plen <= 120)
++              ac_addr->s6_addr32[3] |= htonl((u32)(~0) >> (plen > 92 ? plen % 32 : 0));
++
++      /* RFC2526: for interface identifiers in EUI-64
++       * format, the universal/local bit in the interface
++       * identifier MUST be set to 0. */
++      if (plen == 64) {
++              ac_addr->s6_addr32[2] &= (int)htonl(0xfdffffff);
++      }
++      /* Mobile IPv6 Home-Agents anycast id (0x7e) */
++      ac_addr->s6_addr32[3] &= (int)htonl(0xfffffffe);
++
++      return 0;
++}
++
++#endif /* _UTIL_H */
+diff -uprN linux-2.4.25.old/net/ipv6/ndisc.c linux-2.4.25/net/ipv6/ndisc.c
+--- linux-2.4.25.old/net/ipv6/ndisc.c  2003-11-28 18:26:21.000000000 +0000
++++ linux-2.4.25/net/ipv6/ndisc.c      2004-06-26 11:29:32.000000000 +0100
+@@ -23,6 +23,7 @@
+  *                                            and moved to net/core.
+  *    Pekka Savola                    :       RFC2461 validation
+  *    YOSHIFUJI Hideaki @USAGI        :       Verify ND options properly
++ *    Ville Nuorvala                  :       RFC2461 fixes to proxy ND
+  */
+ /* Set to 3 to get tracing... */
+@@ -70,6 +71,7 @@
+ #include <net/ip6_route.h>
+ #include <net/addrconf.h>
+ #include <net/icmp.h>
++#include <net/mipglue.h>
+ #include <net/checksum.h>
+ #include <linux/proc_fs.h>
+@@ -187,6 +189,8 @@ struct ndisc_options *ndisc_parse_option
+               case ND_OPT_TARGET_LL_ADDR:
+               case ND_OPT_MTU:
+               case ND_OPT_REDIRECT_HDR:
++              case ND_OPT_RTR_ADV_INTERVAL:
++              case ND_OPT_HOME_AGENT_INFO:
+                       if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) {
+                               ND_PRINTK2((KERN_WARNING
+                                           "ndisc_parse_options(): duplicated ND6 option found: type=%d\n",
+@@ -372,8 +376,8 @@ ndisc_build_ll_hdr(struct sk_buff *skb, 
+  */
+ void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
+-                 struct in6_addr *daddr, struct in6_addr *solicited_addr,
+-                 int router, int solicited, int override, int inc_opt) 
++              struct in6_addr *daddr, struct in6_addr *solicited_addr, 
++              int router, int solicited, int override, int inc_opt) 
+ {
+       static struct in6_addr tmpaddr;
+       struct inet6_ifaddr *ifp;
+@@ -766,7 +770,8 @@ void ndisc_recv_ns(struct sk_buff *skb)
+               int addr_type = ipv6_addr_type(saddr);
+               if (in6_dev && in6_dev->cnf.forwarding &&
+-                  (addr_type & IPV6_ADDR_UNICAST) &&
++                  (addr_type & IPV6_ADDR_UNICAST || 
++                   addr_type == IPV6_ADDR_ANY) &&
+                   pneigh_lookup(&nd_tbl, &msg->target, dev, 0)) {
+                       int inc = ipv6_addr_type(daddr)&IPV6_ADDR_MULTICAST;
+@@ -778,13 +783,21 @@ void ndisc_recv_ns(struct sk_buff *skb)
+                                       nd_tbl.stats.rcv_probes_mcast++;
+                               else
+                                       nd_tbl.stats.rcv_probes_ucast++;
+-                                      
+-                              neigh = neigh_event_ns(&nd_tbl, lladdr, saddr, dev);
+-                              if (neigh) {
+-                                      ndisc_send_na(dev, neigh, saddr, &msg->target,
+-                                                    0, 1, 0, 1);
+-                                      neigh_release(neigh);
++                              if (addr_type & IPV6_ADDR_UNICAST) {            
++                                      neigh = neigh_event_ns(&nd_tbl, lladdr, saddr, dev);
++
++                                      if (neigh) {
++                                              ndisc_send_na(dev, neigh, saddr, &msg->target,
++                                                            0, 1, 0, 1);
++                                              neigh_release(neigh);
++                                      }
++                              } else {
++                                      /* the proxy should also protect against DAD */
++                                      struct in6_addr maddr;
++                                      ipv6_addr_all_nodes(&maddr);
++                                      ndisc_send_na(dev, NULL, &maddr, &msg->target,
++                                                    0, 0, 0, 1);
+                               }
+                       } else {
+                               struct sk_buff *n = skb_clone(skb, GFP_ATOMIC);
+@@ -849,6 +862,9 @@ void ndisc_recv_na(struct sk_buff *skb)
+               if (ifp->flags & IFA_F_TENTATIVE) {
+                       addrconf_dad_failure(ifp);
+                       return;
++              } else if (ndisc_mip_mn_ha_probe(ifp, lladdr)) {
++                      in6_ifa_put(ifp);
++                      return;
+               }
+               /* What should we make now? The advertisement
+                  is invalid, but ndisc specs say nothing
+@@ -887,6 +903,7 @@ void ndisc_recv_na(struct sk_buff *skb)
+                            msg->icmph.icmp6_override, 1);
+               neigh_release(neigh);
+       }
++      ndisc_check_mipv6_dad(&msg->target);
+ }
+ static void ndisc_router_discovery(struct sk_buff *skb)
+@@ -894,6 +911,7 @@ static void ndisc_router_discovery(struc
+         struct ra_msg *ra_msg = (struct ra_msg *) skb->h.raw;
+       struct neighbour *neigh;
+       struct inet6_dev *in6_dev;
++      int change_rtr;
+       struct rt6_info *rt;
+       int lifetime;
+       struct ndisc_options ndopts;
+@@ -923,10 +941,6 @@ static void ndisc_router_discovery(struc
+               ND_PRINTK1("RA: can't find in6 device\n");
+               return;
+       }
+-      if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_ra) {
+-              in6_dev_put(in6_dev);
+-              return;
+-      }
+       if (!ndisc_parse_options(opt, optlen, &ndopts)) {
+               in6_dev_put(in6_dev);
+@@ -935,7 +949,12 @@ static void ndisc_router_discovery(struc
+                                  "ICMP6 RA: invalid ND option, ignored.\n");
+               return;
+       }
++      change_rtr = ndisc_mipv6_ra_rcv(skb, &ndopts);
++      if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_ra) {
++              in6_dev_put(in6_dev);
++              return;
++      }
+       if (in6_dev->if_flags & IF_RS_SENT) {
+               /*
+                *      flag that an RA was received after an RS was sent
+@@ -963,8 +982,7 @@ static void ndisc_router_discovery(struc
+               ip6_del_rt(rt, NULL);
+               rt = NULL;
+       }
+-
+-      if (rt == NULL && lifetime) {
++      if (rt == NULL && lifetime && change_rtr) {
+               ND_PRINTK2("ndisc_rdisc: adding default router\n");
+               rt = rt6_add_dflt_router(&skb->nh.ipv6h->saddr, skb->dev);
+@@ -1087,6 +1105,8 @@ out:
+       if (rt)
+               dst_release(&rt->u.dst);
+       in6_dev_put(in6_dev);
++
++      ndisc_mipv6_change_router(change_rtr);
+ }
+ static void ndisc_redirect_rcv(struct sk_buff *skb)
+diff -uprN linux-2.4.25.old/net/ipv6/raw.c linux-2.4.25/net/ipv6/raw.c
+--- linux-2.4.25.old/net/ipv6/raw.c    2003-11-28 18:26:21.000000000 +0000
++++ linux-2.4.25/net/ipv6/raw.c        2004-06-26 11:29:32.000000000 +0100
+@@ -43,6 +43,7 @@
+ #include <net/transp_v6.h>
+ #include <net/udp.h>
+ #include <net/inet_common.h>
++#include <net/mipglue.h>
+ #include <net/rawv6.h>
+@@ -636,6 +637,7 @@ static int rawv6_sendmsg(struct sock *sk
+                       hdr.daddr = daddr;
+               else
+                       hdr.daddr = NULL;
++              hdr.daddr = mipv6_get_fake_hdr_daddr(hdr.daddr, daddr);
+               err = ip6_build_xmit(sk, rawv6_frag_cksum, &hdr, &fl, len,
+                                    opt, hlimit, msg->msg_flags);
+diff -uprN linux-2.4.25.old/net/ipv6/route.c linux-2.4.25/net/ipv6/route.c
+--- linux-2.4.25.old/net/ipv6/route.c  2004-02-18 13:36:32.000000000 +0000
++++ linux-2.4.25/net/ipv6/route.c      2004-06-26 11:29:32.000000000 +0100
+@@ -49,6 +49,7 @@
+ #include <net/addrconf.h>
+ #include <net/tcp.h>
+ #include <linux/rtnetlink.h>
++#include <net/mipglue.h>
+ #include <asm/uaccess.h>
+@@ -363,12 +364,8 @@ static struct rt6_info *rt6_cow(struct r
+               rt->u.dst.flags |= DST_HOST;
+ #ifdef CONFIG_IPV6_SUBTREES
+-              if (rt->rt6i_src.plen && saddr) {
+-                      ipv6_addr_copy(&rt->rt6i_src.addr, saddr);
+-                      rt->rt6i_src.plen = 128;
+-              }
++              rt->rt6i_src.plen = ort->rt6i_src.plen;
+ #endif
+-
+               rt->rt6i_nexthop = ndisc_get_neigh(rt->rt6i_dev, &rt->rt6i_gateway);
+               dst_hold(&rt->u.dst);
+@@ -511,14 +508,19 @@ struct dst_entry * ip6_route_output(stru
+       struct rt6_info *rt;
+       int strict;
+       int attempts = 3;
++      struct in6_addr *saddr;
++      if (ipv6_chk_addr(fl->nl_u.ip6_u.daddr, NULL))
++              saddr = NULL;
++      else
++              saddr = fl->nl_u.ip6_u.saddr;
++              
+       strict = ipv6_addr_type(fl->nl_u.ip6_u.daddr) & (IPV6_ADDR_MULTICAST|IPV6_ADDR_LINKLOCAL);
+ relookup:
+       read_lock_bh(&rt6_lock);
+-      fn = fib6_lookup(&ip6_routing_table, fl->nl_u.ip6_u.daddr,
+-                       fl->nl_u.ip6_u.saddr);
++      fn = fib6_lookup(&ip6_routing_table, fl->nl_u.ip6_u.daddr, saddr);
+ restart:
+       rt = fn->leaf;
+@@ -663,25 +665,6 @@ out:
+       return (atomic_read(&ip6_dst_ops.entries) > ip6_rt_max_size);
+ }
+-/* Clean host part of a prefix. Not necessary in radix tree,
+-   but results in cleaner routing tables.
+-
+-   Remove it only when all the things will work!
+- */
+-
+-static void ipv6_addr_prefix(struct in6_addr *pfx,
+-                           const struct in6_addr *addr, int plen)
+-{
+-      int b = plen&0x7;
+-      int o = plen>>3;
+-
+-      memcpy(pfx->s6_addr, addr, o);
+-      if (o < 16)
+-              memset(pfx->s6_addr + o, 0, 16 - o);
+-      if (b != 0)
+-              pfx->s6_addr[o] = addr->s6_addr[o]&(0xff00 >> b);
+-}
+-
+ static int ipv6_get_mtu(struct net_device *dev)
+ {
+       int mtu = IPV6_MIN_MTU;
+@@ -810,7 +793,7 @@ int ip6_route_add(struct in6_rtmsg *rtms
+                       if (!(gwa_type&IPV6_ADDR_UNICAST))
+                               goto out;
+-                      grt = rt6_lookup(gw_addr, NULL, rtmsg->rtmsg_ifindex, 1);
++                      grt = rt6_lookup(gw_addr, &rtmsg->rtmsg_src, rtmsg->rtmsg_ifindex, 1);
+                       err = -EHOSTUNREACH;
+                       if (grt == NULL)
+@@ -848,7 +831,15 @@ int ip6_route_add(struct in6_rtmsg *rtms
+                       goto out;
+               }
+       }
+-
++#ifdef USE_IPV6_MOBILITY
++        /* If destination is mobile node, add special skb->dst->input
++         * function for proxy ND.
++         */
++        if (rtmsg->rtmsg_flags & RTF_MOBILENODE) {
++                rt->u.dst.input = ip6_mipv6_forward;
++        }
++#endif /* CONFIG_IPV6_MOBILITY */
++        
+       if (ipv6_addr_is_multicast(&rt->rt6i_dst.addr))
+               rt->rt6i_hoplimit = IPV6_DEFAULT_MCASTHOPS;
+       else
+@@ -936,7 +927,7 @@ void rt6_redirect(struct in6_addr *dest,
+       struct rt6_info *rt, *nrt;
+       /* Locate old route to this destination. */
+-      rt = rt6_lookup(dest, NULL, neigh->dev->ifindex, 1);
++      rt = rt6_lookup(dest, saddr, neigh->dev->ifindex, 1);
+       if (rt == NULL)
+               return;
+@@ -1003,6 +994,9 @@ source_ok:
+       nrt = ip6_rt_copy(rt);
+       if (nrt == NULL)
+               goto out;
++#ifdef CONFIG_IPV6_SUBTREES
++      nrt->rt6i_src.plen = rt->rt6i_src.plen;
++#endif
+       nrt->rt6i_flags = RTF_GATEWAY|RTF_UP|RTF_DYNAMIC|RTF_CACHE;
+       if (on_link)
+@@ -1104,6 +1098,9 @@ void rt6_pmtu_discovery(struct in6_addr 
+               nrt = ip6_rt_copy(rt);
+               if (nrt == NULL)
+                       goto out;
++#ifdef CONFIG_IPV6_SUBTREES
++              nrt->rt6i_src.plen = rt->rt6i_src.plen;
++#endif
+               ipv6_addr_copy(&nrt->rt6i_dst.addr, daddr);
+               nrt->rt6i_dst.plen = 128;
+               nrt->u.dst.flags |= DST_HOST;
+diff -uprN linux-2.4.25.old/net/ipv6/tcp_ipv6.c linux-2.4.25/net/ipv6/tcp_ipv6.c
+--- linux-2.4.25.old/net/ipv6/tcp_ipv6.c       2003-11-28 18:26:21.000000000 +0000
++++ linux-2.4.25/net/ipv6/tcp_ipv6.c   2004-06-26 11:29:32.000000000 +0100
+@@ -50,6 +50,7 @@
+ #include <net/addrconf.h>
+ #include <net/ip6_route.h>
+ #include <net/inet_ecn.h>
++#include <net/mipglue.h>
+ #include <asm/uaccess.h>
+@@ -557,6 +558,7 @@ static int tcp_v6_connect(struct sock *s
+       struct flowi fl;
+       struct dst_entry *dst;
+       int addr_type;
++      int reroute = 0;
+       int err;
+       if (addr_len < SIN6_LEN_RFC2133) 
+@@ -660,7 +662,7 @@ static int tcp_v6_connect(struct sock *s
+       fl.proto = IPPROTO_TCP;
+       fl.fl6_dst = &np->daddr;
+-      fl.fl6_src = saddr;
++      fl.fl6_src = saddr; 
+       fl.oif = sk->bound_dev_if;
+       fl.uli_u.ports.dport = usin->sin6_port;
+       fl.uli_u.ports.sport = sk->sport;
+@@ -669,31 +671,46 @@ static int tcp_v6_connect(struct sock *s
+               struct rt0_hdr *rt0 = (struct rt0_hdr *) np->opt->srcrt;
+               fl.nl_u.ip6_u.daddr = rt0->addr;
+       }
+-
+       dst = ip6_route_output(sk, &fl);
+-
++#ifdef CONFIG_IPV6_SUBTREES
++      reroute = (saddr == NULL);
++#endif
+       if ((err = dst->error) != 0) {
+               dst_release(dst);
+               goto failure;
+       }
+-
+-      ip6_dst_store(sk, dst, NULL);
+-      sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM;
+-
++      if (!reroute) {
++              ip6_dst_store(sk, dst, NULL, NULL);
++              sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM;
++      }
+       if (saddr == NULL) {
+               err = ipv6_get_saddr(dst, &np->daddr, &saddr_buf);
++
++              if (reroute)
++                      dst_release(dst);
+               if (err)
+                       goto failure;
+               saddr = &saddr_buf;
++              ipv6_addr_copy(&np->rcv_saddr, saddr);
++#ifdef CONFIG_IPV6_SUBTREES
++              fl.fl6_src = saddr; 
++              dst = ip6_route_output(sk, &fl);
++
++              if ((err = dst->error) != 0) {
++                      dst_release(dst);
++                      goto failure;
++              }
++              ip6_dst_store(sk, dst, NULL, NULL);
++              sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM;
++#endif
+       }
+       /* set the source address */
+-      ipv6_addr_copy(&np->rcv_saddr, saddr);
+       ipv6_addr_copy(&np->saddr, saddr);
+       sk->rcv_saddr= LOOPBACK4_IPV6;
+-      tp->ext_header_len = 0;
++      tp->ext_header_len = tcp_v6_get_mipv6_header_len();
+       if (np->opt)
+               tp->ext_header_len = np->opt->opt_flen+np->opt->opt_nflen;
+       tp->mss_clamp = IPV6_MIN_MTU - sizeof(struct tcphdr) - sizeof(struct ipv6hdr);
+@@ -1338,7 +1355,7 @@ static struct sock * tcp_v6_syn_recv_soc
+ #endif
+       MOD_INC_USE_COUNT;
+-      ip6_dst_store(newsk, dst, NULL);
++      ip6_dst_store(newsk, dst, NULL, NULL);
+       sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM;
+       newtp = &(newsk->tp_pinfo.af_tcp);
+@@ -1383,7 +1400,7 @@ static struct sock * tcp_v6_syn_recv_soc
+                       sock_kfree_s(sk, opt, opt->tot_len);
+       }
+-      newtp->ext_header_len = 0;
++      newtp->ext_header_len = tcp_v6_get_mipv6_header_len();
+       if (np->opt)
+               newtp->ext_header_len = np->opt->opt_nflen + np->opt->opt_flen;
+@@ -1710,7 +1727,7 @@ static int tcp_v6_rebuild_header(struct 
+                       return err;
+               }
+-              ip6_dst_store(sk, dst, NULL);
++              ip6_dst_store(sk, dst, NULL, NULL);
+               sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM;
+       }
+@@ -1749,7 +1766,7 @@ static int tcp_v6_xmit(struct sk_buff *s
+                       return -sk->err_soft;
+               }
+-              ip6_dst_store(sk, dst, NULL);
++              ip6_dst_store(sk, dst, NULL, NULL);
+       }
+       skb->dst = dst_clone(dst);
+diff -uprN linux-2.4.25.old/net/ipv6/udp.c linux-2.4.25/net/ipv6/udp.c
+--- linux-2.4.25.old/net/ipv6/udp.c    2004-02-18 13:36:32.000000000 +0000
++++ linux-2.4.25/net/ipv6/udp.c        2004-06-26 11:29:32.000000000 +0100
+@@ -48,6 +48,7 @@
+ #include <net/ip.h>
+ #include <net/udp.h>
+ #include <net/inet_common.h>
++#include <net/mipglue.h>
+ #include <net/checksum.h>
+@@ -232,6 +233,7 @@ int udpv6_connect(struct sock *sk, struc
+       struct ip6_flowlabel    *flowlabel = NULL;
+       int                     addr_type;
+       int                     err;
++      int                     reroute = 0;
+       if (usin->sin6_family == AF_INET) {
+               if (__ipv6_only_sock(sk))
+@@ -331,7 +333,7 @@ ipv4_connected:
+       fl.proto = IPPROTO_UDP;
+       fl.fl6_dst = &np->daddr;
+-      fl.fl6_src = &saddr;
++      fl.fl6_src = NULL;
+       fl.oif = sk->bound_dev_if;
+       fl.uli_u.ports.dport = sk->dport;
+       fl.uli_u.ports.sport = sk->sport;
+@@ -348,29 +350,44 @@ ipv4_connected:
+               struct rt0_hdr *rt0 = (struct rt0_hdr *) np->opt->srcrt;
+               fl.fl6_dst = rt0->addr;
+       }
+-
+       dst = ip6_route_output(sk, &fl);
+-
+       if ((err = dst->error) != 0) {
+               dst_release(dst);
+               fl6_sock_release(flowlabel);
+-              return err;
+-      }
+-
+-      ip6_dst_store(sk, dst, fl.fl6_dst);
+-
++              return err; 
++      } 
++#ifdef CONFIG_IPV6_SUBTREES
++      reroute = (fl.fl6_src == NULL);
++#endif
+       /* get the source adddress used in the apropriate device */
+       err = ipv6_get_saddr(dst, daddr, &saddr);
++      if (reroute)
++              dst_release(dst);
++
+       if (err == 0) {
+-              if(ipv6_addr_any(&np->saddr))
++#ifdef CONFIG_IPV6_SUBTREES
++              if (reroute) {
++                      fl.fl6_src = &saddr;
++                      dst = ip6_route_output(sk, &fl);
++                      if ((err = dst->error) != 0) {
++                              dst_release(dst);
++                              fl6_sock_release(flowlabel);
++                              return err; 
++                      } 
++              }
++#endif
++              if(ipv6_addr_any(&np->saddr)) {
+                       ipv6_addr_copy(&np->saddr, &saddr);
+-
++                      fl.fl6_src = &np->saddr;
++              }
+               if(ipv6_addr_any(&np->rcv_saddr)) {
+                       ipv6_addr_copy(&np->rcv_saddr, &saddr);
+                       sk->rcv_saddr = LOOPBACK4_IPV6;
+               }
++              ip6_dst_store(sk, dst, fl.fl6_dst, 
++                            fl.fl6_src == &np->saddr ? fl.fl6_src : NULL);
+               sk->state = TCP_ESTABLISHED;
+       }
+       fl6_sock_release(flowlabel);
+@@ -894,6 +911,7 @@ static int udpv6_sendmsg(struct sock *sk
+               opt = fl6_merge_options(&opt_space, flowlabel, opt);
+       if (opt && opt->srcrt)
+               udh.daddr = daddr;
++      udh.daddr = mipv6_get_fake_hdr_daddr(udh.daddr, daddr);
+       udh.uh.source = sk->sport;
+       udh.uh.len = len < 0x10000 ? htons(len) : 0;
+diff -uprN linux-2.4.25.old/net/netsyms.c linux-2.4.25/net/netsyms.c
+--- linux-2.4.25.old/net/netsyms.c     2003-11-28 18:26:21.000000000 +0000
++++ linux-2.4.25/net/netsyms.c 2004-06-26 11:29:32.000000000 +0100
+@@ -190,6 +190,7 @@ EXPORT_SYMBOL(neigh_sysctl_register);
+ #endif
+ EXPORT_SYMBOL(pneigh_lookup);
+ EXPORT_SYMBOL(pneigh_enqueue);
++EXPORT_SYMBOL(pneigh_delete);
+ EXPORT_SYMBOL(neigh_destroy);
+ EXPORT_SYMBOL(neigh_parms_alloc);
+ EXPORT_SYMBOL(neigh_parms_release);
index 36ab045..44e1e20 100644 (file)
@@ -1,5 +1,5 @@
 #
-# Automatically generated make config: don't edit
+# Automatically generated by make menuconfig: don't edit
 #
 CONFIG_ARM=y
 # CONFIG_EISA is not set
@@ -49,10 +49,6 @@ CONFIG_ARCH_SA1100=y
 #
 # Archimedes/A5000 Implementations
 #
-
-#
-# Archimedes/A5000 Implementations (select only ONE)
-#
 # CONFIG_ARCH_ARC is not set
 # CONFIG_ARCH_A5K is not set
 
@@ -147,10 +143,6 @@ CONFIG_SA1100_USB_CHAR=y
 # CONFIG_FOOTBRIDGE is not set
 # CONFIG_FOOTBRIDGE_HOST is not set
 # CONFIG_FOOTBRIDGE_ADDIN is not set
-
-#
-# Processor Type
-#
 CONFIG_CPU_32=y
 # CONFIG_CPU_26 is not set
 # CONFIG_CPU_ARM610 is not set
@@ -167,10 +159,6 @@ CONFIG_CPU_32=y
 CONFIG_CPU_SA1100=y
 # CONFIG_CPU_32v3 is not set
 CONFIG_CPU_32v4=y
-
-#
-# Processor Features
-#
 CONFIG_DISCONTIGMEM=y
 
 #
@@ -204,10 +192,6 @@ CONFIG_NET=y
 CONFIG_SYSVIPC=y
 # CONFIG_BSD_PROCESS_ACCT is not set
 CONFIG_SYSCTL=y
-
-#
-# At least one math emulation must be selected
-#
 CONFIG_FPE_NWFPE=y
 # CONFIG_FPE_NWFPE_XP is not set
 # CONFIG_FPE_FASTFPE is not set
@@ -239,10 +223,6 @@ CONFIG_MTD_CONCAT=y
 # CONFIG_MTD_REDBOOT_PARTS is not set
 CONFIG_MTD_CMDLINE_PARTS=y
 # CONFIG_MTD_AFS_PARTS is not set
-
-#
-# User Modules And Translation Layers
-#
 CONFIG_MTD_CHAR=y
 CONFIG_MTD_BLOCK=y
 # CONFIG_FTL is not set
@@ -304,10 +284,6 @@ CONFIG_MTD_SA1100=y
 # CONFIG_MTD_SLRAM is not set
 # CONFIG_MTD_MTDRAM is not set
 # CONFIG_MTD_BLKMTD is not set
-
-#
-# Disk-On-Chip Device Drivers
-#
 # CONFIG_MTD_DOC1000 is not set
 # CONFIG_MTD_DOC2000 is not set
 # CONFIG_MTD_DOC2001 is not set
@@ -336,8 +312,8 @@ CONFIG_MTD_SA1100=y
 # CONFIG_CISS_MONITOR_THREAD is not set
 # CONFIG_BLK_DEV_DAC960 is not set
 # CONFIG_BLK_DEV_UMEM is not set
-CONFIG_BLK_DEV_LOOP=y
-# CONFIG_BLK_DEV_NBD is not set
+CONFIG_BLK_DEV_LOOP=m
+CONFIG_BLK_DEV_NBD=m
 # CONFIG_BLK_DEV_RAM is not set
 # CONFIG_BLK_DEV_INITRD is not set
 # CONFIG_BLK_STATS is not set
@@ -365,14 +341,16 @@ CONFIG_NETFILTER=y
 CONFIG_FILTER=y
 CONFIG_UNIX=y
 CONFIG_INET=y
-# CONFIG_IP_MULTICAST is not set
+CONFIG_IP_MULTICAST=y
 # CONFIG_IP_ADVANCED_ROUTER is not set
 CONFIG_IP_PNP=y
 CONFIG_IP_PNP_DHCP=y
 CONFIG_IP_PNP_BOOTP=y
 CONFIG_IP_PNP_RARP=y
 CONFIG_NET_IPIP=m
-# CONFIG_NET_IPGRE is not set
+CONFIG_NET_IPGRE=m
+# CONFIG_NET_IPGRE_BROADCAST is not set
+# CONFIG_IP_MROUTE is not set
 CONFIG_ARPD=y
 CONFIG_INET_ECN=y
 CONFIG_SYN_COOKIES=y
@@ -438,10 +416,6 @@ CONFIG_IP_NF_ARP_MANGLE=m
 CONFIG_IP_VS=m
 # CONFIG_IP_VS_DEBUG is not set
 CONFIG_IP_VS_TAB_BITS=12
-
-#
-# IPVS scheduler
-#
 CONFIG_IP_VS_RR=m
 CONFIG_IP_VS_WRR=m
 CONFIG_IP_VS_LC=m
@@ -452,18 +426,37 @@ CONFIG_IP_VS_DH=m
 CONFIG_IP_VS_SH=m
 CONFIG_IP_VS_SED=m
 CONFIG_IP_VS_NQ=m
-
-#
-# IPVS application helper
-#
 CONFIG_IP_VS_FTP=m
 CONFIG_IPV6=m
+CONFIG_IPV6_SUBTREES=y
+CONFIG_IPV6_TUNNEL=m
+CONFIG_IPV6_MOBILITY=m
+CONFIG_IPV6_MOBILITY_MN=m
+CONFIG_IPV6_MOBILITY_HA=m
+CONFIG_IPV6_MOBILITY_DEBUG=y
 
 #
 #   IPv6: Netfilter Configuration
 #
 # CONFIG_IP6_NF_QUEUE is not set
-# CONFIG_IP6_NF_IPTABLES is not set
+CONFIG_IP6_NF_IPTABLES=m
+CONFIG_IP6_NF_MATCH_LIMIT=m
+CONFIG_IP6_NF_MATCH_MAC=m
+CONFIG_IP6_NF_MATCH_RT=m
+CONFIG_IP6_NF_MATCH_OPTS=m
+CONFIG_IP6_NF_MATCH_FRAG=m
+CONFIG_IP6_NF_MATCH_HL=m
+CONFIG_IP6_NF_MATCH_MULTIPORT=m
+CONFIG_IP6_NF_MATCH_OWNER=m
+CONFIG_IP6_NF_MATCH_MARK=m
+CONFIG_IP6_NF_MATCH_IPV6HEADER=m
+CONFIG_IP6_NF_MATCH_AHESP=m
+CONFIG_IP6_NF_MATCH_LENGTH=m
+CONFIG_IP6_NF_MATCH_EUI64=m
+CONFIG_IP6_NF_FILTER=m
+CONFIG_IP6_NF_TARGET_LOG=m
+CONFIG_IP6_NF_MANGLE=m
+CONFIG_IP6_NF_TARGET_MARK=m
 # CONFIG_KHTTPD is not set
 
 #
@@ -473,10 +466,6 @@ CONFIG_IPV6_SCTP__=m
 # CONFIG_IP_SCTP is not set
 # CONFIG_ATM is not set
 CONFIG_VLAN_8021Q=m
-
-#
-#  
-#
 # CONFIG_IPX is not set
 # CONFIG_ATALK is not set
 
@@ -505,17 +494,9 @@ CONFIG_VLAN_8021Q=m
 #
 # CONFIG_NET_PKTGEN is not set
 CONFIG_KLIPS=m
-
-#
-# IPSec options (FreeS/WAN)
-#
 CONFIG_KLIPS_AUTH_HMAC_MD5=y
 CONFIG_KLIPS_AUTH_HMAC_SHA1=y
 CONFIG_KLIPS_ENC_3DES=y
-
-#
-#  ESP always enabled with tunnel mode
-#
 CONFIG_KLIPS_IPCOMP=y
 CONFIG_KLIPS_DEBUG=y
 
@@ -626,10 +607,6 @@ CONFIG_NET_RADIO=y
 # CONFIG_AIRONET4500_PROC is not set
 # CONFIG_AIRO is not set
 # CONFIG_HERMES is not set
-
-#
-# Wireless Pcmcia cards support
-#
 # CONFIG_PCMCIA_HERMES is not set
 # CONFIG_AIRO_CS is not set
 CONFIG_NET_WIRELESS=y
@@ -651,14 +628,14 @@ CONFIG_NET_WIRELESS=y
 # PCMCIA network device support
 #
 CONFIG_NET_PCMCIA=y
-CONFIG_PCMCIA_3C589=y
-CONFIG_PCMCIA_3C574=y
-CONFIG_PCMCIA_FMVJ18X=y
-CONFIG_PCMCIA_PCNET=y
-CONFIG_PCMCIA_AXNET=y
-CONFIG_PCMCIA_NMCLAN=y
-CONFIG_PCMCIA_SMC91C92=y
-CONFIG_PCMCIA_XIRC2PS=y
+CONFIG_PCMCIA_3C589=m
+CONFIG_PCMCIA_3C574=m
+CONFIG_PCMCIA_FMVJ18X=m
+CONFIG_PCMCIA_PCNET=m
+CONFIG_PCMCIA_AXNET=m
+CONFIG_PCMCIA_NMCLAN=m
+CONFIG_PCMCIA_SMC91C92=m
+CONFIG_PCMCIA_XIRC2PS=m
 # CONFIG_ARCNET_COM20020_CS is not set
 # CONFIG_PCMCIA_IBMTR is not set
 CONFIG_NET_PCMCIA_RADIO=y
@@ -675,19 +652,11 @@ CONFIG_NET_PCMCIA_RADIO=y
 #
 # IrDA (infrared) support
 #
-CONFIG_IRDA=y
-
-#
-# IrDA protocols
-#
-CONFIG_IRLAN=y
-# CONFIG_IRNET is not set
-CONFIG_IRCOMM=y
+CONFIG_IRDA=m
+CONFIG_IRLAN=m
+CONFIG_IRNET=m
+CONFIG_IRCOMM=m
 CONFIG_IRDA_ULTRA=y
-
-#
-# IrDA options
-#
 CONFIG_IRDA_CACHE_LAST_LSAP=y
 CONFIG_IRDA_FAST_RR=y
 # CONFIG_IRDA_DEBUG is not set
@@ -695,21 +664,9 @@ CONFIG_IRDA_FAST_RR=y
 #
 # Infrared-port device drivers
 #
-
-#
-# SIR device drivers
-#
-CONFIG_IRTTY_SIR=y
-CONFIG_IRPORT_SIR=y
-
-#
-# Dongle support
-#
+CONFIG_IRTTY_SIR=m
+CONFIG_IRPORT_SIR=m
 # CONFIG_DONGLE is not set
-
-#
-# FIR device drivers
-#
 # CONFIG_USB_IRDA is not set
 # CONFIG_NSC_FIR is not set
 # CONFIG_WINBOND_FIR is not set
@@ -719,36 +676,28 @@ CONFIG_IRPORT_SIR=y
 # CONFIG_ALI_FIR is not set
 # CONFIG_VLSI_FIR is not set
 # CONFIG_VIA_IRCC_FIR is not set
-CONFIG_SA1100_FIR=y
+CONFIG_SA1100_FIR=m
 
 #
 # ATA/ATAPI/MFM/RLL support
 #
-CONFIG_IDE=y
+CONFIG_IDE=m
 
 #
 # IDE, ATA and ATAPI Block devices
 #
-CONFIG_BLK_DEV_IDE=y
-
-#
-# Please see Documentation/ide.txt for help/info on IDE drives
-#
+CONFIG_BLK_DEV_IDE=m
 # CONFIG_BLK_DEV_HD_IDE is not set
 # CONFIG_BLK_DEV_HD is not set
-CONFIG_BLK_DEV_IDEDISK=y
+CONFIG_BLK_DEV_IDEDISK=m
 # CONFIG_IDEDISK_MULTI_MODE is not set
 # CONFIG_IDEDISK_STROKE is not set
-CONFIG_BLK_DEV_IDECS=y
+CONFIG_BLK_DEV_IDECS=m
 # CONFIG_BLK_DEV_IDECD is not set
 # CONFIG_BLK_DEV_IDETAPE is not set
 # CONFIG_BLK_DEV_IDEFLOPPY is not set
 # CONFIG_BLK_DEV_IDESCSI is not set
 # CONFIG_IDE_TASK_IOCTL is not set
-
-#
-# IDE chipset support/bugfixes
-#
 # CONFIG_BLK_DEV_CMD640 is not set
 # CONFIG_BLK_DEV_CMD640_ENHANCED is not set
 # CONFIG_BLK_DEV_ISAPNP is not set
@@ -844,10 +793,6 @@ CONFIG_UNIX98_PTY_COUNT=32
 # CONFIG_L3 is not set
 # CONFIG_L3_ALGOBIT is not set
 # CONFIG_L3_BIT_SA1100_GPIO is not set
-
-#
-# Other L3 adapters
-#
 # CONFIG_L3_SA1111 is not set
 # CONFIG_BIT_SA1100_GPIO is not set
 
@@ -861,14 +806,6 @@ CONFIG_UNIX98_PTY_COUNT=32
 # Joysticks
 #
 # CONFIG_INPUT_GAMEPORT is not set
-
-#
-# Input core support is needed for gameports
-#
-
-#
-# Input core support is needed for joysticks
-#
 # CONFIG_QIC02_TAPE is not set
 # CONFIG_IPMI_HANDLER is not set
 # CONFIG_IPMI_PANIC_EVENT is not set
index 1026222..85b865d 100644 (file)
@@ -5,7 +5,7 @@ KV = "${@oe.data.getVar('PV',d,True).split('-')[0]}"
 VRSV = "${@oe.data.getVar('PV',d,True).split('-')[1]}"
 PXAV = "${@oe.data.getVar('PV',d,True).split('-')[2]}"
 JPMV = "${@oe.data.getVar('PV',d,True).split('-')[3]}"
-PR = "r4"
+PR = "r5"
 
 FILESDIR = "${@os.path.dirname(oe.data.getVar('FILE',d,1))}/opensimpad-${PV}"
 
@@ -17,7 +17,8 @@ SRC_URI = "ftp://ftp.kernel.org/pub/linux/kernel/v2.4/linux-${KV}.tar.bz2 \
           file://disable-pcmcia-probe.patch;patch=1 \
            file://mkdep.patch;patch=1 \
            file://defconfig-${MACHINE} \
-          ftp://ftp.xs4all.nl/pub/crypto/freeswan/kernpatch/freeswan-2.06.k2.4.patch.gz;patch=1"
+          ftp://ftp.xs4all.nl/pub/crypto/freeswan/kernpatch/freeswan-2.06.k2.4.patch.gz;patch=1 \
+           file://mipv6-1.1-v2.4.25.patch;patch=1"
 
 # apply this when we have a patch that allows building with gcc 3.x:
 # SRC_URI_append = file://gcc-3.3.patch;patch=1