#
# Patch managed by http://www.holgerschurig.de/patcher.html
#
--- linux-2.4.27/Documentation/Configure.help~mipv6-1.1-v2.4.26
+++ linux-2.4.27/Documentation/Configure.help
@@ -6308,6 +6308,57 @@
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
--- linux-2.4.27/Documentation/DocBook/Makefile~mipv6-1.1-v2.4.26
+++ linux-2.4.27/Documentation/DocBook/Makefile
@@ -2,7 +2,7 @@
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 libata.sgml
+ journal-api.sgml libata.sgml mip6-func.sgml
PS := $(patsubst %.sgml, %.ps, $(BOOKS))
PDF := $(patsubst %.sgml, %.pdf, $(BOOKS))
@@ -96,6 +96,9 @@
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 \
--- /dev/null
+++ linux-2.4.27/Documentation/DocBook/mip6-func.tmpl
@@ -0,0 +1,756 @@
+
+
+
+ MIPL Mobile IPv6 Function Reference Guide
+
+
+
+ MIPL Mobile IPv6 for Linux Team
+
+ Helsinki University of Technology
+ Telecommunications Software and Multimedia Lab
+
+ PO BOX 9201
+ FIN-02015 HUT
+ Finland
+ mipl@list.mipl.mediapoli.com
+
+
+
+
+
+
+ 2000-2001
+ Helsinki University of Technology
+
+
+
+
+ Copyright (c) 2001, 2002 MIPL Mobile IPv6 for Linux Team.
+
+
+ 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 .
+
+
+
+
+
+
+
+
+ Introduction
+
+
+ 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.
+
+
+
+ MIPL is fully open source, licensed under the GNU General
+ Public License. Latest source for MIPL can be downloaded from
+ the MIPL website at:
+
+
+ http://www.mipl.mediapoli.com/.
+
+
+ Developers and users interested in MIPL can subscribe to the
+ MIPL mailing list by sending e-mail to
+ majordomo@list.mipl.mediapoli.com with
+
+
+ subscribe mipl
+
+
+ in the body of the message.
+
+
+
+ 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.
+
+
+
+ MIPL Mobile IPv6 for Linux Team members (past and present):
+
+
+
+
+ Sami Kivisaari Sami.Kivisaari@hut.fi
+
+
+
+
+ Niklas Kampe Niklas.Kampe@hut.fi
+
+
+
+
+ Juha Mynttinen Juha.Mynttinen@hut.fi
+
+
+
+
+ Toni Nykanen Toni.Nykanen@iki.fi
+
+
+
+
+ Henrik Petander Henrik.Petander@hut.fi
+
+
+
+
+ Antti Tuominen ajtuomin@tml.hut.fi
+
+
+
+
+
+
+
+ Marko Myllynen
+
+
+
+
+ Ville Nuorvala vnuorval@tcs.hut.fi
+
+
+
+
+ Jaakko Laine Jaakko.Laine@hut.fi
+
+
+
+
+
+
+
+
+ Common functions for all entities
+
+ Low-level functions
+
+ 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.
+
+!Inet/ipv6/mobile_ip6/mempool.h
+!Inet/ipv6/mobile_ip6/hashlist.h
+
+
+ Debug functions
+
+ Debug and utility functions. These functions are available if
+ CONFIG_IPV6_MOBILITY_DEBUG is set.
+ Otherwise macros expand to no operation.
+
+!Inet/ipv6/mobile_ip6/debug.h
+!Inet/ipv6/mobile_ip6/mipv6.c
+
+
+ Extension Header functions
+
+ These functions create and handle extension headers that are
+ specific to MIPv6.
+
+!Inet/ipv6/mobile_ip6/exthdrs.c
+
+
+ Mobility Header functions
+
+ 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.
+
+!Inet/ipv6/mobile_ip6/sendopts.c
+!Inet/ipv6/mobile_ip6/mh_recv.c
+!Inet/ipv6/mobile_ip6/auth_subopt.c
+
+
+ Binding Cache
+
+ All Mobile IPv6 entities have a binding cache. These functions
+ provide easy manipulation of the binding cache.
+
+!Inet/ipv6/mobile_ip6/bcache.c
+
+
+ Security
+
+
+ These functions are common authentication functions and
+ implement Draft 13 style IPSec AH support for Binding Updates.
+
+!Inet/ipv6/mobile_ip6/ah_algo.c
+!Inet/ipv6/mobile_ip6/sadb.c
+!Inet/ipv6/mobile_ip6/ah.c
+
+
+ Utility functions
+
+
+ These functions are general utility functions commonly used by
+ all entities.
+
+!Inet/ipv6/mobile_ip6/util.c
+
+
+
+
+
+ Mobile Node functions
+ General functions
+
+
+!Inet/ipv6/mobile_ip6/mn.c
+
+
+ Binding Update List
+
+ Mobile Node keeps track of sent binding updates in Binding
+ Update List.
+
+!Inet/ipv6/mobile_ip6/bul.c
+
+
+ Movement detection
+
+
+ These functions are used by the mobile node for movement
+ detection.
+
+!Inet/ipv6/mobile_ip6/mdetect.c
+
+
+
+
+ Home Agent functions
+ General functions
+
+
+!Inet/ipv6/mobile_ip6/ha.c
+
+
+ Duplicate Address Detection functions
+
+ Home Agent does Duplicate Address Detection for Mobile Nodes'
+ addresses. These functions implement MIPv6 specific DAD
+ functionality.
+
+!Inet/ipv6/mobile_ip6/dad.c
+
+
+
+
+ GNU Free Documentation License
+
+
+ Version 1.1, March 2000
+
+
+
+ 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.
+
+
+ 0. PREAMBLE
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 1. APPLICABILITY AND DEFINITIONS
+
+
+ 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".
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 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".
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 2. VERBATIM COPYING
+
+
+ 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.
+
+
+
+ You may also lend copies, under the same conditions stated
+ above, and you may publicly display copies.
+
+
+
+ 3. COPYING IN QUANTITY
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 4. MODIFICATIONS
+
+
+ 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:
+
+
+
+
+
+
+ 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.
+
+
+
+
+ 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).
+
+
+
+
+ C. State on the Title page the name of the publisher of the
+ Modified Version, as the publisher.
+
+
+
+
+ D. Preserve all the copyright notices of the Document.
+
+
+
+
+ E. Add an appropriate copyright notice for your
+ modifications adjacent to the other copyright notices.
+
+
+
+
+ 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.
+
+
+
+
+ G. Preserve in that license notice the full lists of
+ Invariant Sections and required Cover Texts given in the
+ Document's license notice.
+
+
+
+
+ H. Include an unaltered copy of this License.
+
+
+
+
+ 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.
+
+
+
+
+ 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.
+
+
+
+
+ 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.
+
+
+
+
+ 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.
+
+
+
+
+ M. Delete any section entitled "Endorsements". Such a
+ section may not be included in the Modified Version.
+
+
+
+
+ N. Do not retitle any existing section as "Endorsements" or
+ to conflict in title with any Invariant Section.
+
+
+
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 5. COMBINING DOCUMENTS
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 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."
+
+
+
+ 6. COLLECTIONS OF DOCUMENTS
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 7. AGGREGATION WITH INDEPENDENT WORKS
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 8. TRANSLATION
+
+
+ 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.
+
+
+
+ 9. TERMINATION
+
+
+ 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.
+
+
+
+ 10. FUTURE REVISIONS OF THIS LICENSE
+
+
+ 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/.
+
+
+
+ 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.
+
+
+
+
+
--- linux-2.4.27/include/linux/icmpv6.h~mipv6-1.1-v2.4.26
+++ linux-2.4.27/include/linux/icmpv6.h
@@ -40,14 +40,16 @@
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 "
#endif
@@ -70,6 +72,7 @@
#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
};
--- linux-2.4.27/include/linux/if_arp.h~mipv6-1.1-v2.4.26
+++ linux-2.4.27/include/linux/if_arp.h
@@ -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 */
--- linux-2.4.27/include/linux/in6.h~mipv6-1.1-v2.4.26
+++ linux-2.4.27/include/linux/in6.h
@@ -142,6 +142,11 @@
#define IPV6_TLV_JUMBO 194
/*
+ * Mobile IPv6 TLV options.
+ */
+#define MIPV6_TLV_HOMEADDR 201
+
+/*
* IPV6 socket options
*/
--- linux-2.4.27/include/linux/ipv6.h~mipv6-1.1-v2.4.26
+++ linux-2.4.27/include/linux/ipv6.h
@@ -29,6 +29,7 @@
#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 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 @@
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
--- linux-2.4.27/include/linux/ipv6_route.h~mipv6-1.1-v2.4.26
+++ linux-2.4.27/include/linux/ipv6_route.h
@@ -33,6 +33,7 @@
#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
--- /dev/null
+++ linux-2.4.27/include/linux/ipv6_tunnel.h
@@ -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
--- linux-2.4.27/include/linux/rtnetlink.h~mipv6-1.1-v2.4.26
+++ linux-2.4.27/include/linux/rtnetlink.h
@@ -315,6 +315,7 @@
IFA_BROADCAST,
IFA_ANYCAST,
IFA_CACHEINFO,
+ IFA_HOMEAGENT,
__IFA_MAX
};
@@ -324,6 +325,7 @@
#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
--- linux-2.4.27/include/linux/skbuff.h~mipv6-1.1-v2.4.26
+++ linux-2.4.27/include/linux/skbuff.h
@@ -177,7 +177,7 @@
* 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;
--- linux-2.4.27/include/linux/sysctl.h~mipv6-1.1-v2.4.26
+++ linux-2.4.27/include/linux/sysctl.h
@@ -404,6 +404,23 @@
NET_IPV6_ICMP=19,
NET_IPV6_BINDV6ONLY=20,
NET_IPV6_MLD_MAX_MSF=25,
+ 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 {
--- linux-2.4.27/include/net/addrconf.h~mipv6-1.1-v2.4.26
+++ linux-2.4.27/include/net/addrconf.h
@@ -16,9 +16,11 @@
#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 @@
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_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 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)
{
--- linux-2.4.27/include/net/ip6_route.h~mipv6-1.1-v2.4.26
+++ linux-2.4.27/include/net/ip6_route.h
@@ -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 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 @@
*/
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 @@
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);
}
--- linux-2.4.27/include/net/ipv6.h~mipv6-1.1-v2.4.26
+++ linux-2.4.27/include/net/ipv6.h
@@ -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
@@ -146,9 +147,12 @@
__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;
@@ -257,6 +261,38 @@
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
*/
--- /dev/null
+++ linux-2.4.27/include/net/ipv6_tunnel.h
@@ -0,0 +1,92 @@
+/*
+ * $Id$
+ */
+
+#ifndef _NET_IPV6_TUNNEL_H
+#define _NET_IPV6_TUNNEL_H
+
+#include
+#include
+#include
+#include
+#include
+
+/* 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
--- /dev/null
+++ linux-2.4.27/include/net/mipglue.h
@@ -0,0 +1,266 @@
+/*
+ * Glue for Mobility support integration to IPv6
+ *
+ * Authors:
+ * Antti Tuominen
+ *
+ * $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
+#include
+#include
+
+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 */
--- /dev/null
+++ linux-2.4.27/include/net/mipv6.h
@@ -0,0 +1,258 @@
+/*
+ * Mobile IPv6 header-file
+ *
+ * Authors:
+ * Sami Kivisaari
+ *
+ * $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
+#include
+#include
+
+/*
+ *
+ * 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 */
--- linux-2.4.27/include/net/ndisc.h~mipv6-1.1-v2.4.26
+++ linux-2.4.27/include/net/ndisc.h
@@ -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 @@
} __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 @@
#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 @@
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);
--- linux-2.4.27/include/net/sock.h~mipv6-1.1-v2.4.26
+++ linux-2.4.27/include/net/sock.h
@@ -149,7 +149,9 @@
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;
--- linux-2.4.27/net/Makefile~mipv6-1.1-v2.4.26
+++ linux-2.4.27/net/Makefile
@@ -7,7 +7,7 @@
O_TARGET := network.o
-mod-subdirs := ipv4/netfilter ipv6/netfilter ipx irda bluetooth atm netlink sched core sctp 802
+mod-subdirs := ipv4/netfilter ipv6/netfilter ipx irda bluetooth atm netlink sched core sctp 802 ipv6
export-objs := netsyms.o
subdir-y := core ethernet
@@ -25,6 +25,7 @@
ifneq ($(CONFIG_IPV6),n)
ifneq ($(CONFIG_IPV6),)
subdir-$(CONFIG_NETFILTER) += ipv6/netfilter
+subdir-$(CONFIG_IPV6_MOBILITY) += ipv6/mobile_ip6
endif
endif
--- linux-2.4.27/net/core/neighbour.c~mipv6-1.1-v2.4.26
+++ linux-2.4.27/net/core/neighbour.c
@@ -386,7 +386,7 @@
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;
--- linux-2.4.27/net/ipv6/Config.in~mipv6-1.1-v2.4.26
+++ linux-2.4.27/net/ipv6/Config.in
@@ -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
--- linux-2.4.27/net/ipv6/Makefile~mipv6-1.1-v2.4.26
+++ linux-2.4.27/net/ipv6/Makefile
@@ -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
-export-objs := ipv6_syms.o
+ifneq ($(CONFIG_IPV6_MOBILITY),n)
+ifneq ($(CONFIG_IPV6_MOBILITY),)
+ipv6-objs += mipglue.o
+endif
+endif
+
+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
--- linux-2.4.27/net/ipv6/addrconf.c~mipv6-1.1-v2.4.26
+++ linux-2.4.27/net/ipv6/addrconf.c
@@ -68,6 +68,8 @@
#include
+#include
+
#define IPV6_MAX_ADDRESSES 16
/* Set to 3 to get tracing... */
@@ -103,9 +105,9 @@
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 @@
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 @@
/* 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 @@
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 @@
}
-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 @@
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 @@
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 @@
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 @@
/*
* 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 @@
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 @@
*/
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 @@
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;
--- linux-2.4.27/net/ipv6/af_inet6.c~mipv6-1.1-v2.4.26
+++ linux-2.4.27/net/ipv6/af_inet6.c
@@ -58,6 +58,9 @@
#include
#include
#include
+#ifdef CONFIG_IPV6_TUNNEL
+#include
+#endif
#include
#include
@@ -646,6 +649,11 @@
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 @@
#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 @@
ip6_route_cleanup();
ipv6_packet_cleanup();
igmp6_cleanup();
+#ifdef CONFIG_IPV6_TUNNEL
+ ip6_tunnel_cleanup();
+#endif
ndisc_cleanup();
icmpv6_cleanup();
#ifdef CONFIG_SYSCTL
--- linux-2.4.27/net/ipv6/exthdrs.c~mipv6-1.1-v2.4.26
+++ linux-2.4.27/net/ipv6/exthdrs.c
@@ -41,6 +41,9 @@
#include
#include
+#include
+#include
+
#include
/*
@@ -160,7 +163,8 @@
*****************************/
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
*((char**)&opt2->auth) += dif;
if (opt2->srcrt)
*((char**)&opt2->srcrt) += dif;
+ if (opt2->srcrt2)
+ *((char**)&opt2->srcrt2) += dif;
}
return opt2;
}
--- linux-2.4.27/net/ipv6/icmp.c~mipv6-1.1-v2.4.26
+++ linux-2.4.27/net/ipv6/icmp.c
@@ -61,6 +61,8 @@
#include
#include
+#include
+
#include
#include
@@ -364,6 +366,8 @@
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 @@
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;
@@ -598,6 +602,13 @@
case ICMPV6_MLD2_REPORT:
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");
--- linux-2.4.27/net/ipv6/ip6_fib.c~mipv6-1.1-v2.4.26
+++ linux-2.4.27/net/ipv6/ip6_fib.c
@@ -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
#include
@@ -40,7 +41,6 @@
#include
#define RT6_DEBUG 2
-#undef CONFIG_IPV6_SUBTREES
#if RT6_DEBUG >= 3
#define RT6_TRACE(x...) printk(KERN_DEBUG x)
@@ -500,6 +500,8 @@
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.
* /
@@ -508,17 +510,19 @@
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 @@
/* 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 @@
/* 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 @@
}
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 @@
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 @@
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 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 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 @@
}
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();
}
--- linux-2.4.27/net/ipv6/ip6_input.c~mipv6-1.1-v2.4.26
+++ linux-2.4.27/net/ipv6/ip6_input.c
@@ -40,13 +40,42 @@
#include
#include
+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);
}
--- linux-2.4.27/net/ipv6/ip6_output.c~mipv6-1.1-v2.4.26
+++ linux-2.4.27/net/ipv6/ip6_output.c
@@ -50,6 +50,8 @@
#include
#include
+#include
+
static __inline__ void ipv6_select_ident(struct sk_buff *skb, struct frag_hdr *fhdr)
{
static u32 ipv6_fragmentation_id = 1;
@@ -194,7 +196,14 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
* 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 @@
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.
*/
--- linux-2.4.27/net/ipv6/ipv6_syms.c~mipv6-1.1-v2.4.26
+++ linux-2.4.27/net/ipv6/ipv6_syms.c
@@ -6,6 +6,8 @@
#include
#include
#include
+#include
+#include
EXPORT_SYMBOL(ipv6_addr_type);
EXPORT_SYMBOL(icmpv6_send);
@@ -35,3 +37,47 @@
EXPORT_SYMBOL(in6_dev_finish_destroy);
EXPORT_SYMBOL(ipv6_skip_exthdr);
+#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
--- /dev/null
+++ linux-2.4.27/net/ipv6/ipv6_tunnel.c
@@ -0,0 +1,1604 @@
+/*
+ * IPv6 over IPv6 tunnel device
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Ville Nuorvala
+ *
+ * $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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+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(®->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(®->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
+
--- /dev/null
+++ linux-2.4.27/net/ipv6/mipglue.c
@@ -0,0 +1,63 @@
+/*
+ * Glue for Mobility support integration to IPv6
+ *
+ * Authors:
+ * Antti Tuominen
+ *
+ * $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
+
+#include
+#include
+#include
+#include
+
+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;
+}
+
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/Config.in
@@ -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
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/Makefile
@@ -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)
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/README
@@ -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
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/auth_opt.c
@@ -0,0 +1,121 @@
+/*
+ * MIPv6 Binding Authentication Data Option functions
+ *
+ * Authors:
+ * Henrik Petander
+ *
+ * $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
+#include
+#include
+
+#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;
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/bcache.c
@@ -0,0 +1,746 @@
+/*
+ * Binding Cache
+ *
+ * Authors:
+ * Juha Mynttinen
+ *
+ * $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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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;
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/bcache.h
@@ -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
+#include
+#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 */
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/bul.c
@@ -0,0 +1,634 @@
+/*
+ * Binding update list
+ *
+ * Authors:
+ * Juha Mynttinen
+ *
+ * $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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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");
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/bul.h
@@ -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 */
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/config.h
@@ -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;
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/debug.h
@@ -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
+
+/* 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
+ *
+ * $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
+#include
+
+#include
+#include
+#include
+#include
+
+#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;
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/exthdrs.h
@@ -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 */
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/ha.c
@@ -0,0 +1,553 @@
+/*
+ * Home-agent functionality
+ *
+ * Authors:
+ * Sami Kivisaari
+ * Henrik Petander
+ *
+ * $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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#ifdef CONFIG_SYSCTL
+#include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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;
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/ha.h
@@ -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
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/halist.c
@@ -0,0 +1,507 @@
+/*
+ * Home Agents List
+ *
+ * Authors:
+ * Antti Tuominen
+ *
+ * $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
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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);
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/halist.h
@@ -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 */
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/hashlist.c
@@ -0,0 +1,351 @@
+/*
+ * Generic hashtable with chaining. Supports secodary sort order
+ * with doubly linked-list.
+ *
+ * Authors:
+ * Sami Kivisaari
+ * Antti Tuominen
+ *
+ * $Id: s.hashlist.c 1.21 02/10/07 19:31:52+03:00 antti@traci.mipl.mediapoli.com $
+ *
+ * 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
+#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);
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/hashlist.h
@@ -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
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/hmac.c
@@ -0,0 +1,658 @@
+/* Authentication algorithms
+ *
+ * Authors:
+ * Alexis Olivereau
+ *
+ * $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
+#include
+#include
+#include
+#include
+
+#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 */
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/hmac.h
@@ -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
+#include
+
+#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 */
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/ioctl_mn.c
@@ -0,0 +1,142 @@
+/*
+ * Mobile Node IOCTL Control device
+ *
+ * Authors:
+ * Henrik Petander
+ *
+ * $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
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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);
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/mdetect.c
@@ -0,0 +1,1153 @@
+/*
+ * Movement Detection Module
+ *
+ * Authors:
+ * Henrik Petander
+ *
+ * $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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#ifdef CONFIG_SYSCTL
+#include
+#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;
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/mdetect.h
@@ -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 */
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/mipv6_icmp.c
@@ -0,0 +1,342 @@
+/**
+ * Generic icmp routines
+ *
+ * Authors:
+ * Jaakko Laine ,
+ * Ville Nuorvala
+ *
+ * $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
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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 */
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/mipv6_icmp.h
@@ -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
+#include
+
+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
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/mipv6_icmp_ha.c
@@ -0,0 +1,158 @@
+/*
+ * Home Agent specific ICMP routines
+ *
+ * Authors:
+ * Antti Tuominen
+ * Jaakko Laine
+ *
+ * $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
+#include
+#include
+#include
+#include
+#include
+
+#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
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/mipv6_icmp_mn.c
@@ -0,0 +1,273 @@
+/*
+ * Mobile Node specific ICMP routines
+ *
+ * Authors:
+ * Antti Tuominen
+ * Jaakko Laine
+ *
+ * $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
+#include
+#include
+#include
+#include
+
+#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
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/mn.c
@@ -0,0 +1,1521 @@
+/*
+ * Mobile-node functionality
+ *
+ * Authors:
+ * Sami Kivisaari
+ *
+ * $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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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();
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/mn.h
@@ -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
+
+/* 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 */
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/mobhdr.h
@@ -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
+
+/* 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 */
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/mobhdr_common.c
@@ -0,0 +1,1210 @@
+/*
+ * Mobile IPv6 Mobility Header Common Functions
+ *
+ * Authors:
+ * Antti Tuominen
+ *
+ * $Id: s.mh_recv.c 1.159 02/10/16 15:01:29+03:00 antti@traci.mipl.mediapoli.com $
+ *
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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));
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/mobhdr_mn.c
@@ -0,0 +1,1155 @@
+/*
+ * Mobile IPv6 Mobility Header Functions for Mobile Node
+ *
+ * Authors:
+ * Antti Tuominen
+ * Niklas Kämpe
+ * Henrik Petander
+ *
+ * $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
+#include
+#include
+#include
+#include
+#include
+
+#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);
+}
--- /dev/null
+++ linux-2.4.27/net/ipv6/mobile_ip6/module_cn.c
@@ -0,0 +1,167 @@
+/*
+ * Mobile IPv6 Common Module
+ *
+ * Authors:
+ * Sami Kivisaari
+ * Antti Tuominen
+ *
+ * $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
+#include