From 5c067ae5d1a701a9d9de02c488c495b5a22036ed Mon Sep 17 00:00:00 2001 From: Rick van Rein Date: Sun, 23 Dec 2012 15:36:22 +0100 Subject: [PATCH] First version for general use. No servers at fixed IPv6 addresses yet. --- COPYRIGHT | 674 +++++++++++++++++ README | 83 +++ pom.xml | 79 ++ .../openfortress/socket6bed4/ConnectionPool.java | 175 +++++ .../socket6bed4/DatagramSocket6bed4.java | 333 +++++++++ .../openfortress/socket6bed4/Inet6bed4Address.java | 92 +++ .../nl/openfortress/socket6bed4/NeighborCache.java | 755 +++++++++++++++++++ .../nl/openfortress/socket6bed4/ServerNode.java | 769 ++++++++++++++++++++ .../nl/openfortress/socket6bed4/Socket6bed4.java | 14 + .../java/nl/openfortress/socket6bed4/Utils.java | 146 ++++ 10 files changed, 3120 insertions(+) create mode 100644 COPYRIGHT create mode 100644 README create mode 100644 pom.xml create mode 100644 src/main/java/nl/openfortress/socket6bed4/ConnectionPool.java create mode 100644 src/main/java/nl/openfortress/socket6bed4/DatagramSocket6bed4.java create mode 100644 src/main/java/nl/openfortress/socket6bed4/Inet6bed4Address.java create mode 100644 src/main/java/nl/openfortress/socket6bed4/NeighborCache.java create mode 100644 src/main/java/nl/openfortress/socket6bed4/ServerNode.java create mode 100644 src/main/java/nl/openfortress/socket6bed4/Socket6bed4.java create mode 100644 src/main/java/nl/openfortress/socket6bed4/Utils.java diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public 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. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README b/README new file mode 100644 index 0000000..5dc6ddd --- /dev/null +++ b/README @@ -0,0 +1,83 @@ +Socket6bed4 README +================== + +Socket6bed4 is a Java package that introduces an IPv6 tunnel as a socket subclass. +There is no need to configure anything, just adapt your datagram factory to the +new tunnel and you will be able to use IPv6. + + +Purpose +------- + +Java is used in many places, including some that will remain IPv4-only for years +to come. The 6bed4 tunnel is a zero-config tunnel that eliminates dependency on +your ISP so you can assume that IPv6 is always available. Or better even, you +can distribute Java software that is IPv6-only by using 6bed4 as a fallback for +users that don't have native IPv6 yet. + + +About 6bed4 +----------- + +The design of 6bed4 is intended to support zero-config operation. You are welcome +to setup your private 6bed4 server though; it will still be able to communicate +with the rest of the IPv6 world, native as well as 6bed4. + +The design of 6bed4 also explicitly supports peer-to-peer communication. It will +attempt to setup direct links between clients, which are therefore referred to as +peers instead of clients. The tunnel server is a fallback service, and will only +be used in practice if a symmetric NAT is used anywhere between the peers. This +means that 6bed4 is suitable for realtime media streaming between directly +connected peers. The disadvantages of IPv4 are not transferred to IPv6 through +this tunnel mechanism! + +Until 6bed4 is a formal IETF standard, something that we are indeed working +towards, there is a serious chance that the addresses used will change in the +future. This means that you should keep an eye on such changes and keep the +software using 6bed4 up to date. To help you with that, announcements of +such an infrastructural nature are sent to an extremely low-traffic list, + +https://lists.sourceforge.net/lists/listinfo/tun6bed4-infra + +More information about the 6bed4 tunnel is concentrated at + +http://devel.0cpm.org/6bed4 + +Specifically for this module, you will find information on + +http://devel.0cpm.org/6bed4/java-socket6bed4 + + +Using Socket6bed4 on a peer or client +------------------------------------- + +We prefer to speak of 6bed4 endpoints as peers, rather than clients. This is to +emphasise that they are normally connected to each other, instead of to an +intermediate server. + +Peer use of 6bed4 is straightforward. You should adapt the code that generates +your DatagramSocket to create an instance of nl.openfortress.DatagramSocket6bed4 +instead. That's all. If you are already using a factory, you have to do this in +one place only. + +If you have access to a native IPv6 address, you probably want to prefer using +that. We will not enforce that however -- as we can imagine that you would want +to run native IPv6 and 6bed4 in parallel, to approach other 6bed4 peers in +parallel with yours. + + +Using Socket6bed4 on a server +----------------------------- + +Every run of Java will normally create a different IPv6 address. This address +contains the external IPv4 address and UDP port for use with the server. These +will change every restart of the Socket6bed4 stack, so a server-side address +would not be fixed. This may present a problem; let us know if this is important +for you. + + +Feedback +-------- + +We would like to hear what you use Socket6bed4 for. + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..12a9e22 --- /dev/null +++ b/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + socket6bed4 + nl.openfortress.socket6bed4 + 0.0 + jar + + + UTF-8 + + + + + + + + + + maven-assembly-plugin + 2.2.1 + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + + + + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9 + + + + + + + diff --git a/src/main/java/nl/openfortress/socket6bed4/ConnectionPool.java b/src/main/java/nl/openfortress/socket6bed4/ConnectionPool.java new file mode 100644 index 0000000..6a76ef0 --- /dev/null +++ b/src/main/java/nl/openfortress/socket6bed4/ConnectionPool.java @@ -0,0 +1,175 @@ +package nl.openfortress.socket6bed4; + +import java.net.InetAddress; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; + +import java.util.Hashtable; + + +/** The ConnectionPool links IPv4 sockets for 6bed4 with the implementations + * of IPv6 sockets. Each IPv4 socket may have multiple IPv6 sockets, as + * a result of these variation factors: + * - the router supplies a range of IPv6 addresses + * - the IPv6 DatagramSockets can listen to multiple ports + * - the IPv6 DatagramSockets could receive from multiple ports + * + * Incoming traffic is mapped based on an InetSocketAddress; if an exact + * match is not available, then the zero address is tried instead. + * + * The sender information is stored in a Datagrampacket's SocketAddress. + */ +public class ConnectionPool { + + protected final static byte serveripbytes[] = { (byte) 145, (byte) 136, 0, 1 }; + protected final static int serverport = 25788; + protected InetSocketAddress default_server; + protected Hashtable clients; + protected Hashtable servers; + protected Hashtable addressmap; + + + /** The connection pool is normally a shared resource, + * although private instances are certainly possible. + */ + private static ConnectionPool pool; + + /** Create the shared connection pool when loading this class. + */ + static { + try { + pool = new ConnectionPool (); + } catch (SocketException se) { + throw new RuntimeException ("Failed to create initial default ConnectionPool"); + } + } + + /** Return the connection pool intended to be shared. + */ + public static ConnectionPool getSharedConnectionPool () { + return pool; + } + + + /** Start using a server, possibly introducing it. + * Return the ServerNode that is herewith opened. + */ + public ServerNode openServer (InetSocketAddress isa) + throws UnknownHostException, SocketException { + if (!(isa.getAddress () instanceof Inet4Address)) { + throw new UnknownHostException ("openServer() must use an IPv4 address"); + } + synchronized (servers) { + ServerNode srv = servers.get (isa); + if (srv == null) { + srv = new ServerNode (isa); + servers.put (isa, srv); + srv.useMore (); + addressmap.put (srv.getShared6bed4Address (), isa); //TODO:BOOTSTRAP LOCKUP -- NO SHARED ADDRESS UNTIL MAINTAINER STARTS + } else { + srv.useMore (); + } + return srv; + } + } + + /** Stop using a server, possibly removing it. + */ + public void closeServer (InetSocketAddress isa) + throws UnknownHostException { + if (!(isa.getAddress () instanceof Inet4Address)) { + throw new UnknownHostException ("closeServer() must use an IPv4 address"); + } + synchronized (servers) { + ServerNode srv = servers.get (isa); + if (srv == null) { + throw new UnknownHostException ("Cannot close a new server"); + } + if (srv.useLess ()) { + servers.remove (isa); + addressmap.remove (srv.getShared6bed4Address ()); + } + } + } + + /** Return the default 6bed4 tunnel server. + */ + public InetSocketAddress getDefaultServer () { + return default_server; + } + + /** Return the ServerNode for the default 6bed4 tunnel server. + */ + public ServerNode getDefaultServerNode () { + return servers.get (default_server); + } + + /** Return the ServerNode for a given InetSocketAddress, + * holding the server's IPv4 address and UDP port. + * Returns null if not found. + * TODO: WHAT IS THE PURPOSE HERE?!? + */ + public ServerNode getServerNode (InetSocketAddress isa) { + synchronized (servers) { + return servers.get (isa); + } + } + + /** Returns the ServerNode for a given InetAddress, which + * is then assumed to match an Inet6Address in the + * hash tables. + * Returns null if not found. + */ + public ServerNode getServerNode (InetAddress ia) { + InetSocketAddress isa = addressmap.get (ia); + if (isa == null) { + return null; + } + return servers.get (isa); + } + + /** Change the server that counts as the default tunnel server + * for future connection attempts. Existing connections are not + * influenced. + */ + public void setDefaultServer (InetSocketAddress isa) + throws UnknownHostException, SocketException { + if (!(isa.getAddress () instanceof Inet4Address)) { + throw new UnknownHostException ("setDefaultServer() must use an IPv4 address"); + } + try { + synchronized (servers) { + closeServer (default_server); + default_server = isa; + openServer (default_server); + } + } catch (UnknownHostException uhe) { + throw new RuntimeException ("UseCount error for default 6bed4 tunnel server"); + } + } + + /** Construct a ServerPool with nothing but the default server. + * There are no clients yet, of course. + */ + public ConnectionPool () + throws SocketException { + clients = new Hashtable (); + servers = new Hashtable (); + addressmap = new Hashtable (); + try { + default_server = new InetSocketAddress (InetAddress.getByAddress (serveripbytes), serverport); + } catch (UnknownHostException uhe) { + throw new RuntimeException ("Failed to parse IPv4 address of 6bed4 default server"); + } + try { + openServer (default_server); + } catch (UnknownHostException uhe) { + throw new RuntimeException ("Failed to initialize default 6bed4 tunnel server"); + } + } + +} + diff --git a/src/main/java/nl/openfortress/socket6bed4/DatagramSocket6bed4.java b/src/main/java/nl/openfortress/socket6bed4/DatagramSocket6bed4.java new file mode 100644 index 0000000..92dd30c --- /dev/null +++ b/src/main/java/nl/openfortress/socket6bed4/DatagramSocket6bed4.java @@ -0,0 +1,333 @@ +package nl.openfortress.socket6bed4; + + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.DatagramSocketImpl; +import java.net.InetAddress; +import java.net.Inet6Address; +import java.net.Inet4Address; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.io.IOException; + +import java.util.Arrays; +import java.nio.ByteBuffer; + + +/** Socket6bed4 describes sockets that are run over IPv6. This means + * that a remote IPv6 address can be contacted when there are only + * IPv4 addresses available locally. + * + * A Socket6bed4 can switch between IPv4 transports; it can either send + * to a 6bed4 server, or directly to the targeted peer. Either path is + * acceptable as. The Socket can arrange this automatically, using + * peering attempts through Neighbor Discovery. + * + * Perhaps it's a bit silly to make a 6bed4 DatagramSocket a subclass + * of a plain DatagramSocket. It is very practical however; it means + * that these objects can be substituted anywhere, without question. + */ +public class DatagramSocket6bed4 extends DatagramSocket { + + protected static DatagramSocket server_ipv4socket; + protected DatagramSocket ipv4socket; + protected InetSocketAddress cnx6sa = null; + protected InetSocketAddress my6sa = null; + protected ServerNode my6sn = null; + protected int ephemeral = 3210; + static final byte prephdr_payload [] = { 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, Utils.IPPROTO_UDP, 64 /*HopLimit*/ }; + protected byte prephdr_udp [] = new byte [8]; + + /* Tell this connection to switch back to the default server, + * skipping any direct route. + */ + public void useDefaultServer () { + ipv4socket = server_ipv4socket; + } + + /* Set the IPv4 address and UDP port of the default server, + * as shared by all sockets. Note that sockets will not + * move over immediately. Invoke useDefaultServer() if this + * is needed. + */ + public void setDefaultServer (Inet4Address server4, int port4) + throws SocketException { + DatagramSocket sox = new DatagramSocket (); + sox.connect (server4, port4); + server_ipv4socket = sox; + } + + + /** Return currect remote address */ + public InetAddress getInetAddress () { + return (cnx6sa != null)? cnx6sa.getAddress (): null; + } + + /** Return currect local port */ + public int getPort () { + return (cnx6sa != null)? cnx6sa.getPort (): 0; + } + + /** Return remote sock address */ + public InetSocketAddress getRemoteSocketAddress () { + return cnx6sa; + } + + /** Return local address */ + public InetAddress getLocalAddress () { + return my6sa.getAddress (); + } + + /** Return local port */ + public int getLocalPort () { + return my6sa.getPort (); + } + + /** Return bound local socket address */ + public InetSocketAddress getLocalSocketAddress () { + return my6sa; + } + + public void bind (int port) + throws SocketException { + // + // Verify if binding request is properly formed + if ((port <= 0) || (port > 65535)) { + throw new SocketException ("Binding is only possible to ports from 1 to 65535"); + } + //TODO// Checks against Router Advertisement + // + // Perform the actual binding, and unbind old + ConnectionPool pool = ConnectionPool.getSharedConnectionPool (); + my6sn = pool.getDefaultServerNode (); + synchronized (this) { + my6sn.registerDatagramClient (port); + if (my6sa != null) { + my6sn.unregisterDatagramClient (my6sa.getPort ()); + } + prephdr_udp [0] = (byte) (port >> 8); + prephdr_udp [1] = (byte) (port & 0x00ff); + my6sa = new InetSocketAddress (my6sn.getShared6bed4Address (), port); + } + } + + /** Attempt to bind to an address and/or port, exercising + * 6bed4 constraints as they arise from the router advertisement. + */ + public void bind (InetSocketAddress sa) + throws SocketException { + if (sa == null) { + // + // Iterate over ports until one is found to be free + for (int i=0; i < 4095; i++) { + ephemeral = (ephemeral + 1) & 0x0ffff; + try { + bind (ephemeral + 49152); + return; /* No complaints? Than break out of the loop */ + } catch (SocketException se) { + /* Taken? Then continue trying */ + } + } + throw new SocketException ("No ephemeral ports are free on the 6bed4 address"); + + } else { + bind (sa.getPort ()); + } + } + + /** Are we bound? */ + public boolean isBound () { + return my6sa != null; + } + + /** Connect to a remote IPv6 address and UDP port. */ + public void connect (InetAddress address, int port) { + connect (new InetSocketAddress (address, port)); + } + public void connect (InetSocketAddress addr) { + cnx6sa = (InetSocketAddress) addr; + prephdr_udp [2] = (byte) (cnx6sa.getPort () >> 8); + prephdr_udp [3] = (byte) (cnx6sa.getPort () & 0x00ff); + + } + + /** Disconnect from a remote IPv6 address and UDP port. */ + public void disconnect () { + cnx6sa = null; + } + + /** Are we connected? */ + public boolean isConnected () { + return cnx6sa != null; + } + + + /** The interface for DatagramPackets conceals the UDP layer + * underlaying the actual data exchanged. For 6bed4 however, + * a few headers will have to be prefixed. These are the + * IPv6 header and UDP-over-IPv6 header. The underlying + * DatagramSocket for IPv4 will conceal the UDP-over-IPv4. + * + * Details about playful hints are described in the class + * NeighborCache. In general, the hint should only be given + * for packets that will be re-sent upon failure, and whose + * success of delivery can be reported back. This can then + * be used to setup direct connections to peers without + * explicit negotiation through Neighbor Discovery. You can + * safely set the playful flag on all similar traffic, as it + * will only influence on initial attempts at traffic. A good + * efficiency trade-off is to use playful hints only on + * initiating UDP messages, such as a SIP INVITE. + */ + public void send_playful (DatagramPacket pkt6, boolean playful) + throws IOException { + int pkt6len = pkt6.getLength (); + ByteBuffer buf = ByteBuffer.allocate (Utils.OFS_UDP6_PLOAD + pkt6len); + buf.put (prephdr_payload); + buf.put ( my6sa.getAddress ().getAddress ()); + Inet6Address remote; + if (cnx6sa != null) { + remote = (Inet6Address) cnx6sa.getAddress (); + } else { + remote = (Inet6Address) pkt6.getAddress (); + } + buf.put (remote.getAddress ()); + buf.put (prephdr_udp); + buf.put (pkt6.getData (), pkt6.getOffset (), pkt6len); + byte[] tundata = buf.array (); + tundata [Utils.OFS_IP6_PLEN + 0] = + tundata [Utils.OFS_UDP6_PLEN + 0] = (byte) ((pkt6len + 8) >> 8); + tundata [Utils.OFS_IP6_PLEN + 1] = + tundata [Utils.OFS_UDP6_PLEN + 1] = (byte) ((pkt6len + 8) & 0x00ff); + if (cnx6sa == null) { + int rport = pkt6.getPort (); + tundata [Utils.OFS_UDP6_DSTPORT + 0] = (byte) (rport >> 8); + tundata [Utils.OFS_UDP6_DSTPORT + 1] = (byte) (rport & 0x00ff); + } + int csum = Utils.checksum_udpv6 (tundata); + tundata [Utils.OFS_UDP6_CSUM + 0] = (byte) (csum >> 8); + tundata [Utils.OFS_UDP6_CSUM + 1] = (byte) (csum & 0x00ff); + //TODO// Shared and locked send4 instead of local pkt4 + DatagramPacket pkt4 = new DatagramPacket (tundata, 0, tundata.length); + pkt4.setSocketAddress (my6sn.lookup_neighbor ((Inet6Address) pkt6.getAddress (), playful)); + my6sn.send (pkt4); + } + + + /** Receive a packet from the underlying IPv4 layer. As with + * sending, the UDP underneath the packet is hidden, but for + * 6bed4 the IPv6 and UDP-over-IPv6 headers must be stripped + * and interpreted. Only traffic destined for our own + * combination of IPv6 address and UDP-over-IPv6 port will be + * passed on to us. + * + * The hint returned indicates if the datagram was received + * directly from the peer. This information may be of varying + * use to implementations, but it would be specifically useful + * for confirmation of playfully sent packets, as described + * for the send_playful () method. To this end, the method + * acknowledge_playful () is used to complete the cycle of + * optimistic direct connections to peers. + */ + public boolean receive_playful (DatagramPacket pkt6) + throws IOException { + byte msg[] = my6sn.receive_datagram (my6sa.getPort (), getSoTimeout ()); + if (msg == null) { + throw new SocketTimeoutException ("No data available within timeout"); + } + int len = Utils.fetch_net16 (msg, Utils.OFS_IP6_PLEN) + Utils.OFS_IP6_PLOAD; + if (len < Utils.OFS_UDP6_PLOAD) { + throw new IOException ("Datagram packet with silly short size received over 6bed4 tunnel"); + } + if (Utils.checksum_udpv6 (msg, 0) != Utils.fetch_net16 (msg, Utils.OFS_UDP6_CSUM)) { + throw new IOException ("Datagram packet with faulty checksum received over 6bed4 tunnel"); + } + if (Utils.fetch_net16 (msg, Utils.OFS_UDP6_PLEN) + Utils.OFS_IP6_PLOAD > len) { + throw new IOException ("Incomplete datagram received over 6bed4 tunnel"); + } + pkt6.setAddress ((Inet6Address) InetAddress.getByAddress (Arrays.copyOfRange (msg, Utils.OFS_IP6_SRC, Utils.OFS_IP6_SRC + 16))); + pkt6.setPort (Utils.fetch_net16 (msg, Utils.OFS_UDP6_SRCPORT)); + // PROBABLY FORMALLY CORRECT, BUT NOT HOW PEOPLE USE IT: pkt6.setData (msg, Utils.OFS_UDP6_PLOAD, len); + pkt6.setData (Arrays.copyOfRange (msg, Utils.OFS_UDP6_PLOAD, Utils.OFS_UDP6_PLOAD + len - 48), 0, len - 48); + //TODO// return ! pkt4.getAddress ().equals (ipv4socket.getInetAddress ()); + return false; + } + + /** In a playfully hinting exchange with send_playful() and + * receive_playful(), this is the final acknowledgement that + * should be called upon completion. This acknowledges a + * reliable direct connection to a peer without a need to + * go through explicit Neighbor Discovery. The pkt6 + * parameter holds the remote peer's address. + * + * Note that invoking this function when receive_playful() + * returned false for the exchane is a damn lie, and may + * end up configuring the NeighborCache entry for this + * neighbor with a direct route that does not actually + * function. You will then end up sending messages into + * oblivia. + * + * Also note that plaful sends are an optimistic variation + * that is not strictly necessary; the NeighborCache has + * its own method builtin, based on Neighbor Discovery. + * If a direct link to a peer is possible at all, then + * this will find it. Only if you wish to use the + * optimistic variation to avoid these extra exchanges + * should you consider playful mode. After all this + * discouraging information it should also be noted that + * your network traffic will look extremely cool if it + * manages to get through directly to a 6bed4 peer without + * any explicit negotiation! + */ + public void acknowledge_playful (Inet6Address ia6bed4) { + my6sn.acknowledge_playful (ia6bed4.getAddress(), 0); + } + + /** The "standard" interface for sending bytes, overriding the + * parent function and not sending playful hints. + */ + public void send (DatagramPacket pkt6) + throws IOException { + send_playful (pkt6, false); + } + + /** The "standard" interface for receiving bytes, overriding the + * parent function and not supporting playful operation. + */ + public void receive (DatagramPacket pkt6) + throws IOException { + /*(void)*/ receive_playful (pkt6); + } + + + /** Construct a new DatagramSocket6bed4 based on an underlying + * DatagramSocket for IPv4. + */ + public DatagramSocket6bed4 (InetSocketAddress bindaddr) + throws SocketException { + super (); + bind (bindaddr); + } + public DatagramSocket6bed4 (int port, Inet6Address bindaddr) + throws SocketException { + this (new InetSocketAddress (bindaddr, port)); + } + public DatagramSocket6bed4 (int port) + throws SocketException { + this (port, null /*TODO:INADDR_ANY*/); + } + public DatagramSocket6bed4 () + throws SocketException { + super (); + bind ((InetSocketAddress) null); + } + public DatagramSocket6bed4 (DatagramSocketImpl impl) + throws SocketException { + throw new RuntimeException ("Cannot choose DatagramSocketImpl"); + } + + +} + diff --git a/src/main/java/nl/openfortress/socket6bed4/Inet6bed4Address.java b/src/main/java/nl/openfortress/socket6bed4/Inet6bed4Address.java new file mode 100644 index 0000000..f7e3151 --- /dev/null +++ b/src/main/java/nl/openfortress/socket6bed4/Inet6bed4Address.java @@ -0,0 +1,92 @@ +package nl.openfortress.socket6bed4; + +import java.net.InetAddress; +import java.net.Inet6Address; +import java.net.UnknownHostException; + + +/** The Inet6bed4Address class acts a bit like InetAddress and + * Inet6Address. It is not, however related to these classes; + * they are locked down by the Java language [1]. There are + * a few calls similar to those in Inet*Address that are also + * available here; however, an address created here will be an + * Inet6Address. + * + * [1] Inet6Address is a final class, and InetAddress has no + * constructors to call. Nasty. + */ +public final class Inet6bed4Address { + + /** Returns the shared 6bed4 address for this host. + * Note that a 6bed4 address is available on any host that + * has an IPv4 address and supports IPv6. This is also the + * when a native IPv6 address exists. You should however + * prefer native IPv6 over 6bed4, except perhaps for the + * communication with 6bed4 peers. + */ + public static Inet6Address getLocalHost () + throws UnknownHostException { + return ConnectionPool. + getSharedConnectionPool (). + getDefaultServerNode (). + getShared6bed4Address (); + } + + /** Test if a given InetAddress is a 6bed4 address. + * Note that this does not guarantee that it can be cast + * to an Inet6bed4Address. + * TODO: See notes for is6bed4Address(byte addr[]) + */ + public static boolean is6bed4Address (Inet6Address ia) { + if (! (ia instanceof Inet6Address)) { + return false; + } + //TODO// Quickfix to incorporate active servers, ideally also to be done for the byte[] version, and would ideally not be limited to the shared pool: + if (ConnectionPool.getSharedConnectionPool ().getServerNode (ia) != null) { + return true; + } + return is6bed4Address (ia.getAddress ()); + } + + /** Test if the given byte array represents a 6bed4 address. + * TODO: These addresses are subject to change until 6bed4 + * is formalised as an RFC! Please subscribe to + * tun6bed4-infra for (only those) updates: + * https://lists.sourceforge.net/lists/listinfo/tun6bed4-infra + * The intention is to obtain a /16 for ten years to come, + * and recognise a 6bed4 address based on that. + */ + public static boolean is6bed4Address (byte addr[]) { + if (addr.length != 16) { + return false; + } + //TODO// Take local 6bed4 serverpool into account + return (addr [0] == 0x20) && (addr [1] == 0x01) && (addr [2] == 0x06) && (addr [3] == 0x7c) && (addr [4] == 0x12) && (addr [5] == 0x7c); + } + + /** Return an Inet6bed4Address based on a byte string. + * TODO: Perhaps try sharing with existing instances? + */ + public static Inet6Address getByAddress (byte addr[]) { + //TODO// Instead of just checking length, eventually use is6bed4Address + if (addr.length != 16) { + throw new ClassCastException ("Byte array has wrong length"); + } + if (!is6bed4Address (addr)) { + throw new ClassCastException ("Address is not a 6bed4 address"); + } + try { + return (Inet6Address) InetAddress.getByAddress (addr); + } catch (UnknownHostException uhe) { + throw new RuntimeException ("Failed to parse IPv6 address bytes"); + } + } + + /** A private constructor avoids instantiation. + */ + private Inet6bed4Address () { + ; + } + +} + diff --git a/src/main/java/nl/openfortress/socket6bed4/NeighborCache.java b/src/main/java/nl/openfortress/socket6bed4/NeighborCache.java new file mode 100644 index 0000000..6e545a7 --- /dev/null +++ b/src/main/java/nl/openfortress/socket6bed4/NeighborCache.java @@ -0,0 +1,755 @@ +package nl.openfortress.socket6bed4; + + +import java.util.Hashtable; +import java.util.Queue; +import java.util.ArrayDeque; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.Inet4Address; +import java.net.UnknownHostException; + +import java.io.IOException; + + + +/* The Neighbor Cache is modelled in Java, because Android/Java won't + * give access to the network stack. + * + * The primary task of the cache is to map IPv6-addresses in byte [16] + * format to a destination, which is stored as a Inet4SocketAddress + * for direct use with DatagramPacket facilities in Java. A hash is + * used to gain fast access to the target object. + * + * To find mappings, the cache will send up to 3 Neighor Solicitation + * messages to the direct peer. During this period it will pass through + * states ATTEMPT1 to ATTEMPT3, and finally become FAILED. This is a sign + * that no further attempts need to be made until the expiration of the + * mapping, about 30 seconds after starting it. This process starts + * when an unknown peer is approached, or one that is STALE. + * + * If a Neighbor Advertisement comes in, this information is sent here + * for processing. The cache entry will usually go to REACHABLE in such + * situations. + * + * When traffic comes in with the direct address of this peer as its + * destination, then the cache is informed; if no mapping in REACHABLE + * state exists, then new attempts are started, so as to enable direct + * connectivity after the remote peer has opened a hole to us. + * + * The cache maintains a seperate timer queue for each state, and will + * act upon the objects if they expire. This makes the queue rather + * self-controlled. + * + * Before changing any piece of this code, please assure yourself that + * you understand each single synchronized section, and convince + * yourself that you understand the trick in NeighborQueue.dequeue(). + * This is not just multitasking code; it may well be the most complex + * example of multitasking code that you ever came accross. Do not + * change the code unless you are absolutely certain that you have + * understood all the invariants that make this code work! + */ + +class NeighborCache { + + /* The states of neighbors in the cache. + */ + public final static int ATTEMPT1 = 0; /* Discovery sequence */ + public final static int ATTEMPT2 = 1; + public final static int ATTEMPT3 = 2; + public final static int FAILED = 3; + public final static int REACHABLE = 4; /* Confirmed in the last ~30s */ + public final static int STALE = 5; /* Not used for up to ~30s */ + public final static int NUM_NGB_STATES = 6; /* Count of normal states */ + public final static int INTRANSIT = 7; /* Traveling between queues */ + + + /* The timing spent in the various states of a cache entry. Stable states + * take up several seconds, while waiting for a response is faster. + * Note the remarks at NeighborQueue.dequeue() for an important constraint; + * specifically, STALE is spent in an attempt to recycle an entry as + * REACHABLE, so the time spent in STALE must not exceed the time spent + * in REACHABLE. This avoids having two entries for one neighbor in a + * queue, which would jeapourdise the timeout calculations. Note that + * this problem does not arise when an object occurs in multiple queues + * at once, because a mismatch with the queue's state means that those + * lingering neighbors are ignored as timeout sources. + * + * TODO: I found that Android can be pretty slow in responding over eth0, + * possibly worse over UMTS or (shiver) GPRS. So, it may be that the + * following timing must be slowed down, *or* that NgbAdv ought to be + * accepted in the STALE state. + */ + final static int state_timing_millis [] = { + 50, 50, 50, 30000, /* Discovery -- a few ms between steps */ + 27500, /* STALE -- 30 additional seconds */ + 30000 /* REACHABLE -- 30 seconds of bliss */ + }; + + /* The timer queues for neighbor discovery, each in its own thread */ + NeighborQueue queues []; + + /* The neighbor cache contains all elements, regardless of their state. + * This is needed to avoid doing double work. If a neighbor is considered + * to be available, then it should be in here. Once it stops being of + * interest (STALE or FAILED expires) it will be removed. + * + * Note that the key and value are both Neighbor objects. The only part + * of the Neighbor dictating equality (and the hashCode) is the 6-byte + * key holding the remote peer's IPv4 address and UDP port, so it is + * possible to put one object into the Hashtable as both key and value; + * and then to lookup an entry with a minimally setup Neighbor as key + * to find a more evolved value; the latter will be incorporated into + * the procedures of the Neighbor Cache, including the various queues. + */ + private Hashtable neighbor_cache; + + + /* The public server, used as default return value for queires after + * unsettled neighbor cache entries. + */ + private InetSocketAddress public_server; + + + /* The socket used as uplink to the rest of the 6bed4-using World. + */ + private DatagramSocket uplink; + + + /* The 6bed4 address as a 16-byte array; may also be used as an 8-byte prefix. + */ + private byte address_6bed4 []; + + /* The 6bed4 address as a 6-byte key. + */ + private byte address_6bed4_key []; + + + /* Construct a neighbor cache with no entries but a lot of structure */ + public NeighborCache (DatagramSocket ipv4_uplink, InetSocketAddress pubserver, byte link_address_6bed4 []) { + uplink = ipv4_uplink; + public_server = pubserver; + address_6bed4 = new byte [16]; + Utils.memcp_address (address_6bed4, 0, link_address_6bed4, 0); + address_6bed4_key = new byte [6]; + address2key (address_6bed4, 0, address_6bed4_key); + queues = new NeighborQueue [NUM_NGB_STATES]; + for (int i=0; i < NUM_NGB_STATES; i++) { + queues [i] = new NeighborQueue (this, i); + } + neighbor_cache = new Hashtable (); + } + + /* Destroy a neighbor cache */ + public void cleanup () { + for (int i=0; i < NUM_NGB_STATES; i++) { + queues [i].interrupt (); + } + } + + /* Remove an expired entry from the neighbor cache. + */ + public void remove_from_cache (Neighbor ngb) { + byte key [] = new byte [6]; + socketaddress2key (ngb.target, key); + synchronized (neighbor_cache) { + neighbor_cache.remove (key); + } + } + + /* Attempt to retrieve an entry from the neighbor cache. The resulting + * actions as well as the return value may differ, depending on the + * state of the cache entry. This function always returns immediately, + * with the tunnel server as a default response for instant connections, + * but the cache may asynchronously attempt to find a direct path to the + * neighbor. + * + * The "playful" argument is rather special. When set to true, it + * indicates that an initial experiment attempting to contact the + * neighbor directly is acceptable. This is usually the cae if the + * following conditions apply: + * 1. The message is resent upon failure + * 2. Success can be recognised and reported back here + * Setting this flag causes the first (and only the first) result + * from lookup_neighbor to point directly to the host; resends will + * be sent through the tunnel, while a few neighbor discoveries are + * still tried towards the remote peer, until something comes back + * directly to acknowledge contact. When something comes back over + * a direct route, this should be reported to the neighbor cache + * through received_peer_direct_acknowledgement() so it can + * learn that future traffic can be directly sent to the peer. + * + * A common case for which this holds is TCP connection setup: + * 1. Packets with SYN are sent repeatedly if need be + * 2. Packets with ACK stand out in a crowd + * So, when TCP packets with SYN are sent, their lookup_neighbor() + * invocation can set the playful flag; the first attempt will go + * directly to the peer and hopefully initiate direct contact; if + * not, future attempts are sent through the tunnel. When the + * remote peer accepts, it will return a packet with ACK set over + * the direct route, which is enough reason to signal success to + * the neighbor cache. Interestingly, the first reply message + * from a TCP server will generally hold the SYN flag as well, so + * if the remote 6bed4 stack is also playful about TCP, it is + * likely to send it back directly even if it received the opening + * SYN through the tunnel. This means that even a blocking NAT on + * the remote end will cause a new attempt on return. As usual, + * an ACK from client to server follows, which asserts to the + * server that it can talk directly (because, at that time, the + * client has picked up on the directly sent SYN+ACK). + * + * It is possible for TCP to initiate a connection from both ends + * at the same time. Effectively this splits the SYN+ACK into + * two messages, one with SYN and one with ACK. It may happen + * that direct contact is not correctly established if that is + * done, but the follow-up neighbor discovery that follow on a + * playful SYN that does not return an ACK over the direct route + * will establish NAT-passthrough if it is technically possible. + * A similar thing occurs when the initial SYN is lost for some + * reason. + */ + public InetSocketAddress lookup_neighbor (byte addr [], int addrofs, boolean playful) { + if (!is6bed4 (addr, addrofs)) { + return public_server; + } + boolean puppy = false; + Neighbor ngb; + byte seeker_key [] = new byte [6]; + address2key (addr, addrofs, seeker_key); //TODO// Only used for seeker creation? + int seeker_hash = key2hash (seeker_key); + synchronized (neighbor_cache) { + ngb = neighbor_cache.get (seeker_hash); + if (ngb == null) { + ngb = new Neighbor (seeker_key, playful); //TODO// Possibly seeker.clone () + neighbor_cache.put (seeker_hash, ngb); + puppy = true; + } + } + switch (ngb.state) { + case ATTEMPT1: + case ATTEMPT2: + case ATTEMPT3: + case FAILED: + case INTRANSIT: + // + // No usable entry exists, but no new attempts are needed. + // Simply return the default router's address. + // + // If the new entry was just inserted into the neighbor + // cache for this thread, then enqueue into ATTEMPT1, part + // of which is sending a neighbor solicitation. However, + // if the request is made by a playful puppy, then skip + // sending the Neighbor Discovery upon insertion into the + // ATTEMPT1 queue; the 6bed4 stack will instead send the + // initial message directly to the peer. To that end, return + // the direct neighbor instead of the safe default of the + // public server. Note that a repeat of the packet will + // no longer count as a puppy, so this is done only once. + if (puppy) { + queues [ATTEMPT1].enqueue (ngb, playful); + if (playful) { + return ngb.target; + } + } + return public_server; + case STALE: + // + // The neighbor has been reached directly before; assume this + // is still possible (continue into REACHABLE) but also send a + // neighbor solicitation to keep the lines open. + solicit_neighbor (ngb); + case REACHABLE: + // + // The neighbor is known to be available for direct contact. + // There is no work to be done to that end. + return ngb.target; + default: + return public_server; + } + } + + /* Send a Neighbor Solicitation to the peer. When this succeeds at + * penetrating to the remote peer's 6bed4 stack, then a Neighbor + * Advertisement will return, and be reported through the method + * NeighborCache.received_peer_direct_acknowledgement() so the + * cache can update its state, and support direct sending to the + * remote peer. + * + * Even though this kind of message may be sent after failed + * playful attempts, it still does not rule out the possibility + * of any such attempts returning, so the "playful" flag for + * the Neighbor solicited is not changed. + */ + public void solicit_neighbor (Neighbor ngb) { + byte key [] = new byte [6]; + socketaddress2key (ngb.target, key); + byte ngbsol [] = new byte [72]; + // + // IPv6 header: + ngbsol [0] = 0x60; + ngbsol [1] = ngbsol [2] = ngbsol [3] = 0; + ngbsol [4] = 0; ngbsol [5] = 4 + 28; + ngbsol [6] = Utils.IPPROTO_ICMPV6; + ngbsol [7] = (byte) 255; + Utils.memcp_address (ngbsol, 8, address_6bed4, 0); + key2ipv6address (key, ngbsol, 24); + int ofs = 40; + // ICMPv6 header: + ngbsol [ofs++] = (byte) Utils.ND_NEIGHBOR_SOLICIT; + ngbsol [ofs++] = 0; + ofs += 2; // Checksum + ngbsol [ofs++] = + ngbsol [ofs++] = + ngbsol [ofs++] = + ngbsol [ofs++] = 0x00; + key2ipv6address (ngb.key, ngbsol, ofs); + ofs += 16; + // ICMPv6 Option: Source Link-Layer Address + ngbsol [ofs++] = 1; // SrcLLAddr + ngbsol [ofs++] = 1; // length is 1x 8 bytes + for (int i=0; i<6; i++) { + ngbsol [ofs++] = address_6bed4_key [i]; + } + int csum = Utils.checksum_icmpv6 (ngbsol, 0); + ngbsol [42] = (byte) ((csum >> 8) & 0xff); + ngbsol [43] = (byte) ( csum & 0xff); + try { + DatagramPacket pkt = new DatagramPacket (ngbsol, ngbsol.length, ngb.target); + uplink.send (pkt); + } catch (IOException ioe) { + ; + } + } + + /* The TunnelServer reports having received a neighbor advertisement + * by calling this function. This may signal the cache that the + * corresponding cache entry needs updating. + * + * The "playful" flag is used to indicate that the acknowledgement + * arrived as part of a playful attempt in lookup_neighbor(). This + * information is used to determine if resends have taken place + * through the tunnel server. If such resends were sent, then it + * is not safe anymore to assume that a one-to-one exchange has + * taken place, and it becomes necessary to rely on the generic + * mechanism of Neighbor Discovery to detect the ability for direct + * contact between peers. + */ + public void received_peer_direct_acknowledgement (byte addr [], int addrofs, boolean playful) { + Neighbor ngb; + byte key [] = new byte [6]; + address2key (addr, addrofs, key); + int seeker_hash = key2hash (key); + synchronized (neighbor_cache) { + ngb = neighbor_cache.get (seeker_hash); + } + if (ngb == null) { + return; + } + if (playful && !ngb.playful) { + return; + } + int nst = ngb.state; + switch (nst) { + case ATTEMPT1: + case ATTEMPT2: + case ATTEMPT3: + case STALE: + // + // There is an entry waiting to be resolved. + // Apply the lessons learnt to that entry. + // Be careful -- another thread may be trying + // the same, so we use the dequeue function + // that checks again before proceeding. + if (queues [nst].dequeue (ngb)) { + // + // Only one thread will continue here, having dequeued the neighbor + queues [REACHABLE].enqueue (ngb); + } + break; + case REACHABLE: + case FAILED: + // + // A persistent state has been reached, and any + // advertisements coming in have missed their + // window of opportunity. Note that this may be + // a sign of attempted abuse. So ignore it. + break; + case INTRANSIT: + // + // Some thread is already working on this neighbor, so drop + // this extra notification. + break; + default: + break; + } + } + + + /* The Neighbor class represents individual entries in the NeighborCache. + * Each contains an IPv6 address as 16 bytes, its state, and a timeout for + * its current state queue. + * + * Most of this class is storage, and is publicly accessible by the + * other components in this class/file. + * + * Note that the Neighbor class serves in the hashtable as a key and value; + * but as a key, the only input used are the 6 bytes with the remote peer's + * IPv4 address and UDP port. This means that another object can easily be + * constructed as a search entry, to resolve into the full entry. + * + * TODO: Inner class -- does that mean that the context is copied into this object? That'd be wasteful! + */ + static class Neighbor { + protected int state; + protected InetSocketAddress target; + protected long timeout; + byte key []; + boolean playful; + + /* The hashCode for this object depends solely on the key value. + * Integrate all the bits of the key for optimal spreading. The + * hashCode may only differ if the equals() output below also + * differs. + */ + public int hashCode () { + return key2hash (key); + } + + /* The equals output determines that two Neighbor objects are + * the same if their key matches; that is, if their remote + * IPv4 address and UDP port is the same. It is guaranteed + * that the hashCode is the same for two equal objects; if two + * objects are unequal then their hashCode is *likely* to differ, + * but not guaranteed. + */ + public boolean equals (Object oth) { + try { + Neighbor other = (Neighbor) oth; + for (int i=0; i<5; i++) { + if (this.key [i] != ((Neighbor) other).key [i]) { + return false; + } + } + return true; + } catch (Throwable thr) { + return false; + } + } + + /* Construct a new Neighbor based on its 6-byte key */ + public Neighbor (byte fromkey [], boolean create_playfully) { + key = new byte [6]; + for (int i=0; i<6; i++) { + key [i] = fromkey [i]; + } + state = NeighborCache.ATTEMPT1; + target = key2socketaddress (key); + playful = create_playfully; + } + } + + /* The NeighborQueue inner class represents a timer queue. The objects in this + * queue each have their own timeouts, as specified by state_timing_millis. + * New entries are attached to the back, timeouts are picked up at the front. + * + * The queues have understanding of the various states, and how to handle + * expiration. + */ + class NeighborQueue extends Thread { + + private NeighborCache cache; + private int whoami; + private Queue queue; + + public NeighborQueue (NeighborCache owner, int mystate) { + cache = owner; + whoami = mystate; + queue = new ArrayDeque (); + this.start (); + } + + public void run () { + while (!isInterrupted ()) { + // + // First, fetch the head element (or wait for one to appear). + // Sample its timeout. Even if the head disappears later on, + // any further entries are certain to have a later timeout. + long timeout; + synchronized (queue) { + Neighbor head = null; + while (head == null) { + head = queue.peek (); + if (head == null) { + try { + queue.wait (); + } catch (InterruptedException ie) { + return; + } + } + } + if (head.state == whoami) { + timeout = 0; + } else { + timeout = head.timeout; + } + } + // + // Now wait until the timeout expires. This is not done in + // a synchronous fashion, so the queue can be changed as + // desired by other threads. Note that misplaced items + // in the queue will be removed by the code further down + // before this thread can find rest. + if (timeout != 0) { + try { + sleep (timeout - System.currentTimeMillis ()); + } catch (InterruptedException ie) { + return; + } + } + // + // Now the time has come to remove the head elements that + // have expired. If nothing happened to the head of the + // queue, then this one should at least be removed, but + // following entries may just as well need removal. Do + // this in a queue-synchronous manner to stop other threads + // from altering the queue. + long now = System.currentTimeMillis (); + Queue removed = new ArrayDeque (); + synchronized (queue) { + Neighbor head = queue.peek (); + while ((head != null) && ((head.state != whoami) || (head.timeout <= now))) { + head = queue.remove (); + if (head.state == whoami) { + removed.offer (head); + } // else, misplaced item that can be silently dropped + head = queue.peek (); + } + } + // + // We now have a few elements in a temporary removed list; + // handle those by inserting them into their next lifecycle + // state (possibly meaning, destroy them). + switch (whoami) { + case ATTEMPT3: +/* TODO: Why would we want to remove the remote socket address? + // + // Remove the remote socket address, and continue with + // promotion to the next lifecycle state + for (Neighbor ngb : removed) { + ngb.target = null; + } + */ + case ATTEMPT1: + case ATTEMPT2: + case REACHABLE: + // + // Promote the neighbor to its next lifecycle state + for (Neighbor ngb : removed) { + queues [whoami + 1].enqueue (ngb); + } + break; + case FAILED: + case STALE: + // + // Remove the neighbor from the cache + for (Neighbor ngb : removed) { + cache.remove_from_cache (ngb); + } + break; + default: + break; + } + } + } + + /* Insert a new element into the queue. If this is the first + * entry, be sure to notify the thread that may be waiting for + * something to chew on. + * Note that an entry may be enqueued only once, use dequeue + * to remove it from a possible former queue. + */ + public void enqueue (Neighbor ngb) { + enqueue (ngb, false); + } + + /* Insert a new element into the queue. If this is the first + * entry, be sure to notify the thread that may be waiting for + * something to chew on. + * Note that an entry may be enqueued only once, use dequeue + * to remove it from a possible former queue. + * The flag "no_action" requests that the side-effects that + * may take place after inserting the element in the queue + * should be skipped. + */ + public void enqueue (Neighbor ngb, boolean no_action) { + // + // Initialise the neighbor for this queue + ngb.state = whoami; + ngb.timeout = System.currentTimeMillis () + NeighborCache.state_timing_millis [whoami]; + // + // Insert the Neighbor into the queue + synchronized (queue) { + boolean wasEmpty = queue.isEmpty (); + queue.offer (ngb); + if (wasEmpty) { + queue.notifyAll (); + } + } + // + // Skip the side-effect of insertion is so desired. + if (no_action) { + return; + } + // + // If this queue represents waiting for a reply to Neighbor + // Solicitation, then solicit the neighbor. This is not + // synchronous, because networking does not directly modify + // the queue, is slow and is asynchronous in nature anyway. + switch (whoami) { + case ATTEMPT1: + case ATTEMPT2: + case ATTEMPT3: + cache.solicit_neighbor (ngb); + break; + default: + break; + } + } + + /* Remove an element from the queue without awaiting timer + * expiration. This is only of interest when incoming traffic + * follows a direct route, while our previous attempts ran into + * FAILED because the remote didn't have its ports open for us yet. + * In addition, this may be used when a timer has gone STALE. + * Note that expiration timers on the queue can continue to run, + * they may simply find that the first element has not timed out + * and another cycle is needed for what is then the head. + * The state and timeout of the neighbor entry will not be modified + * by this call, but future enqueueing may do that nonetheless. + * The function returns success; that is, it tells the caller if + * the object was indeed found in the queue. + * + * *** Implementation note *** + * + * Dequeueing is expensive, as it involves going through the list + * of stored items. Keeping the structures up to date to speedup + * this operation is hardly more pleasant. So, we'll use a trick. + * We simply won't dequeue. This means that queues can hold items + * that should have been removed. As long as objects never cycle + * back to this queue before their timers in this queue have + * expired, the misplacement of a queue item could be directly + * inferred from the state, which does not match the queue's state. + * Such misplaced items can be silently removed. Doing this, the + * only remaining task for dequeue is to change the state in an + * atomic fashion -- that is, without risk of overlapping similar + * operations by other threads. As originally intended, there + * must be only one thread that receives true when removing an + * item from a queue that holds it. The loop that removes the + * queue elements from the head also changes to accommodate this + * behaviour. + */ + public boolean dequeue (Neighbor ngb) { + boolean wasthere; + synchronized (queue) { + if (ngb.state != whoami) { + wasthere = false; + } else { + ngb.state = NeighborCache.INTRANSIT; + wasthere = true; + } + } + return wasthere; + } + } + + + /*** + *** UTILITIES + ***/ + + + /* Check if an address is a 6bed4 address. + */ + public boolean is6bed4 (byte addr [], int addrofs) { + if (Utils.memdiff_halfaddr (addr, addrofs, address_6bed4, 0)) { + return false; + } + if ((addr [addrofs + 11] != (byte) 0xff) || (addr [addrofs + 12] != (byte) 0xfe)) { + return false; + } + //TODO// Possibly require that the port number is even + return true; + } + + /* Map an address to a 6-byte key in the provided space. Assume that the + * address is known to be a 6bed4 address. + */ + private static void address2key (byte addr [], int addrofs, byte key []) { + key [0] = (byte) (addr [addrofs + 8] ^ 0x02); + key [1] = addr [addrofs + 9]; + key [2] = addr [addrofs + 10]; + key [3] = addr [addrofs + 13]; + key [4] = addr [addrofs + 14]; + key [5] = addr [addrofs + 15]; + } + + /* Map an InetSocketAddress to a 6-byte key in the provided space. + */ + private static void socketaddress2key (InetSocketAddress sa, byte key []) { + int port = sa.getPort (); + byte addr [] = sa.getAddress ().getAddress (); + key [0] = (byte) (port & 0xff); + key [1] = (byte) (port >> 8 ); + key [2] = addr [0]; + key [3] = addr [1]; + key [4] = addr [2]; + key [5] = addr [3]; + } + + /* Map a key to an IPv6 address, following the 6bed4 structures. + */ + private void key2ipv6address (byte key [], byte addr [], int addrofs) { + for (int i=0; i<8; i++) { + addr [addrofs + i] = address_6bed4 [i]; + } + addr [addrofs + 8] = (byte) (key [0] ^ 0x02); + addr [addrofs + 9] = key [1]; + addr [addrofs + 10] = key [2]; + addr [addrofs + 11] = (byte) 0xff; + addr [addrofs + 12] = (byte) 0xfe; + addr [addrofs + 13] = key [3]; + addr [addrofs + 14] = key [4]; + addr [addrofs + 15] = key [5]; + } + + /* Map a key to an InetSocketAddress. + */ + private static InetSocketAddress key2socketaddress (byte key []) { + int port = (int) (key [0] & 0xff); + port = port | (((int) (key [1] << 8)) & 0xff00); + byte v4addr [] = new byte [4]; + v4addr [0] = key [2]; + v4addr [1] = key [3]; + v4addr [2] = key [4]; + v4addr [3] = key [5]; + try { + Inet4Address remote = (Inet4Address) Inet4Address.getByAddress (v4addr); + return new InetSocketAddress (remote, port); + } catch (UnknownHostException uhe) { + throw new RuntimeException ("Internet Error", uhe); + } + } + + private static int key2hash (byte key []) { + int retval; + retval = key [0] ^ key [3]; + retval <<= 8; + retval += key [1] ^ key [4]; + retval <<= 8; + retval += key [2] ^ key [5]; + return retval; + } + +} diff --git a/src/main/java/nl/openfortress/socket6bed4/ServerNode.java b/src/main/java/nl/openfortress/socket6bed4/ServerNode.java new file mode 100644 index 0000000..ecbfe86 --- /dev/null +++ b/src/main/java/nl/openfortress/socket6bed4/ServerNode.java @@ -0,0 +1,769 @@ +package nl.openfortress.socket6bed4; + + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.InetAddress; +import java.net.Inet6Address; +import java.net.SocketException; +import java.io.IOException; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.TimeUnit; + + +/** Each ServerNode instance serves a connection to a particular 6bed4 + * tunnel server. This is reflected by its superclass, which is the + * principal address serviced -- the IPv4 address and UDP port for the + * server. + * + * As part of the service for a node, there is a neighbor cache. This + * may be compared to having a per-network neighbor cache as part of + * an operating system. Lookups may lead to more direct routes than + * would otherwise be possible. + * + * TODO: Hook up handle_4to6/handle_6to4 methods with receive/send. + * TODO: Handle IPv6 addr chg by throwing an IOException subclass. + * TODO: Establish NeighborCache with lladdr_6bed4, somehow. + */ +public class ServerNode +extends DatagramSocket { + + private int usecount = 0; + protected InetSocketAddress tunserver; + protected NeighborCache ngbcache; + protected Inet6Address sharedAddress; + private Maintainer maintainer; + private Worker worker; + private BlockingQueue udp_clients []; + static byte local_address [] = new byte [16]; + + /** Create a connection to a 6bed4 tunnel server, and keep it active. + */ + public ServerNode (InetSocketAddress isa) + throws SocketException { + super (); + maintainer = new Maintainer (this); + worker = new Worker (this); + tunserver = isa; + udp_clients = new BlockingQueue [65536]; + } + + /** Create a connection to a 6bed4 tunnel server, and keep it active. + * This constructor binds locally to the given UDP port, if possible. + * This may provide a more predictable IPv6 address, but that depends + * on your networking setup. If you are behind NAT, and/or someone + * else is already using the port you will still have a different IPv6 + * address provided by the 6bed4 server, because it "sees" you on + * another UDP port at the outside of NAT. NAT is NAsTy, we know. + * Things may improve when you setup port forwarding, but that would + * involve fixating the LAN host that can run this ServerNode at the + * given IPv6 address. And again, it would depend on your NAT. + */ + public ServerNode (InetSocketAddress isa, int port) + throws SocketException { + super (port); + maintainer = new Maintainer (this); + worker = new Worker (this); + tunserver = isa; + udp_clients = new BlockingQueue [65536]; + } + + + /** Teardown the connection to a 6bed4 tunnel server. + */ + public void stop () { + worker.interrupt (); + maintainer.interrupt (); + if (ngbcache != null) { + ngbcache.cleanup (); + ngbcache = null; /* Intended exceptions on continued use */ + } + } + + /** Increment the use counter */ + public synchronized void useMore () { + if (usecount++ == 0) { + maintainer.start (); + worker.start (); + } + } + + /** Decrement the use counter, return if it is now useless */ + public synchronized boolean useLess () { + if (--usecount == 0) { + maintainer.interrupt (); + worker.interrupt (); + return true; + } + return false; + } + + + /* Register a UDP client for a given port on the default + * IPv6 address as handed out by getDefault6bed4Address (). + * This is a claim to that port, and a SocketException is + * raised if it is not available. + */ + public void registerDatagramClient (int port) + throws SocketException { + synchronized (udp_clients) { + if (udp_clients [port] != null) { + throw new SocketException ("port taken"); + } + udp_clients [port] = new ArrayBlockingQueue (10); + } + } + + /* Unregister a client from a port that it has claimed. + */ + public void unregisterDatagramClient (int port) + throws SocketException { + synchronized (udp_clients) { + if (udp_clients [port] == null) { + throw new SocketException ("port not in use"); + } + //TODO// possibly cleanup the Queue before letting go + udp_clients [port] = null; + } + } + + /** Return the shared 6bed4 address, usable for anyone who + * wants to allocate ports for UDP/TCP through either a + * Socket6bed4 or a DatagramSocket6bed4. This routine will + * block until an address has been obtained. + * + * To allocate recipients on the shared address, use + * registerDatagramClient(port) and, hopefully one day, + * registerStreamClient(port). Raw access is not possible + * with this address; for that, use getUnique6bed4Address() + * as long as supply lasts. + */ + public Inet6Address getShared6bed4Address () { + synchronized (maintainer) { + while (sharedAddress == null) { + try { + maintainer.wait (); + } catch (InterruptedException ie) { + return null; + } + } + } + return sharedAddress; + } + + /** Return a unique 6bed4 address, which is available for + * raw communication over IPv6. Return null if no addresses + * are available anymore. + * Addresses returned from this function can be used as + * raw sockets. They share the communication channel setup + * for getShared6bed4Address() but the contents of the IPv6 + * packet will not be implemented, but rather relayed + * directly to the provided BlockingQueue instance. + * TODO: Sending, NxtHdr, and how to deal with security. + * TODO: Implement. Consider security implications. + */ + public synchronized Inet6Address getUnique6bed4Address (BlockingQueue recv_TODO_OR_KEEP_INTERNAL) { + //TODO// Register the address in the ConnectionPool so getServerNode() will work! + return null; + } + + /** Pull an element from the BlockingQueue for the given + * Datagram port. Block if no input is available. + * The timeout value is in milli-seconds, zero + * representing infinity (just as with setSoTimeout). + */ + public byte[] receive_datagram (int port, int timeout) + throws SocketException { + if (udp_clients [port] == null) { + throw new SocketException ("port is not allocated"); + } + try { + if (timeout > 0) { + return udp_clients [port].poll (timeout, TimeUnit.MILLISECONDS); + } else { + return udp_clients [port].take (); + } + } catch (InterruptedException ie) { + return null; + } + } + + /** Acknowledge a playful exchange to the Neighbor Cache. + * This is a complex protocol, involving an interaction + * with the application layer, to confirm initial messages + * that support resends. Please see the documentation in + * NeighborCache and DatagramSocket6bed4 for details. + */ + public void acknowledge_playful (byte addr[], int ofs) { + if ((ngbcache != null) && (ofs + 16 <= addr.length)) { + ngbcache.received_peer_direct_acknowledgement (addr, ofs, true); + } + } + + /** Lookup a neighbor. Forwards to NeighborCache. + */ + public InetSocketAddress lookup_neighbor (Inet6Address ia, boolean playful) { + while (ngbcache == null) { + synchronized (maintainer) { + try { + maintainer.wait (); + } catch (InterruptedException ie) { + return null; + } + } + } + return ngbcache.lookup_neighbor (ia.getAddress (), 0, playful); + } + + + public void handle_4to6_nd (byte pkt [], int pktlen, SocketAddress src) + throws IOException, SocketException { + if (Utils.checksum_icmpv6 (pkt, 0) != Utils.fetch_net16 (pkt, Utils.OFS_ICMP6_CSUM)) { + // Checksum is off + return; + } + switch (pkt [Utils.OFS_ICMP6_TYPE]) { + // + // Handle Router Solicitation by dropping it -- this is a peer, not a router + case Utils.ND_ROUTER_SOLICIT: + return; + // + // Handle Router Advertisement as an addressing offering -- but validate the sender + case Utils.ND_ROUTER_ADVERT: + if (pktlen < 40 + 16 + 16) { + // Too short to contain IPv6, ICMPv6 RtrAdv and Prefix Option + return; + } + if ((pkt [Utils.OFS_ICMP6_DATA+1] & 0x80) != 0x00) { + // Indecent proposal to use DHCPv6 over 6bed4 + return; + } + if (Utils.memdiff_addr (pkt, Utils.OFS_IP6_SRC, Utils.router_linklocal_address, 0)) { + // Sender is not 0xfe80::/128 + return; + } + if (Utils.memdiff_halfaddr (pkt, Utils.OFS_IP6_DST, Utils.router_linklocal_address, 0)) { + // Receiver address is not 0xfe80::/64 + return; + } + if ((pkt [Utils.OFS_IP6_DST + 11] != (byte) 0xff) || (pkt [Utils.OFS_IP6_DST + 12] != (byte) 0xfe)) { + // No MAC-based destination address ending in ....:..ff:fe..:.... + return; + } + //TODO// Check if offered address looks like a multicast-address (MAC byte 0 is odd) + //TODO// Check Secure ND on incoming Router Advertisement? + // + // Having validated the Router Advertisement, process its contents + int destprefix_ofs = 0; + int rdofs = Utils.OFS_ICMP6_DATA + 12; + //TODO:+4_WRONG?// while (rdofs <= ntohs (v4v6plen) + 4) { ... } + while (rdofs + 4 < pktlen) { + if (pkt [rdofs + 1] == 0) { + return; /* zero length option */ + } + if (pkt [rdofs + 0] != Utils.ND_OPT_PREFIX_INFORMATION) { + /* skip to next option */ + } else if (pkt [rdofs + 1] != 4) { + return; /* bad length field */ + } else if (rdofs + (pkt [rdofs + 1] << 3) > pktlen + 4) { + return; /* out of packet length */ + } else if ((pkt [rdofs + 3] & (byte) 0xc0) != (byte) 0xc0) { + /* no on-link autoconfig prefix */ + } else if (pkt [rdofs + 2] != 64) { + return; + } else { + destprefix_ofs = rdofs + 16; + } + rdofs += (pkt [rdofs + 1] << 3); + } + if (destprefix_ofs > 0) { + for (int i=0; i<8; i++) { + local_address [0 + i] = pkt [destprefix_ofs + i]; + local_address [8 + i] = pkt [Utils.OFS_IP6_DST + 8 + i]; + } + sharedAddress = (Inet6Address) InetAddress.getByAddress (local_address); + //TODO// syslog (LOG_INFO, "%s: Assigning address %s to tunnel\n", program, v6prefix); + // update_local_netconfig (); + if (ngbcache != null) { + ngbcache.cleanup (); + } + ngbcache = new NeighborCache (this, tunserver, local_address); + maintainer.have_local_address (true); + synchronized (maintainer) { + maintainer.notifyAll (); + } + // Log.i (TAG, "Assigned address to tunnel"); + } + return; + // + // Neighbor Solicitation is an attempt to reach us peer-to-peer, and should be responded to + case Utils.ND_NEIGHBOR_SOLICIT: + if (pktlen < 24) { + // Too short to make sense + return; + } + if (Utils.memdiff_addr (pkt, Utils.OFS_ICMP6_NGBSOL_TARGET, local_address, 0)) { + // Neighbor Solicitation not aimed at me + return; + } + if ((ngbcache == null) || !ngbcache.is6bed4 (pkt, Utils.OFS_IP6_SRC)) { + // Source is not a 6bed4 address + return; + } + // + // Not checked here: IPv4/UDP source versus IPv6 source address (already done) + // Not checked here: LLaddr in NgbSol -- simply send back to IPv6 src address + // + Utils.memcp_address (pkt, Utils.OFS_IP6_DST, pkt, Utils.OFS_IP6_SRC); + Utils.memcp_address (pkt, Utils.OFS_IP6_SRC, local_address, 0); + pkt [Utils.OFS_ICMP6_TYPE] = Utils.ND_NEIGHBOR_ADVERT; + pkt [Utils.OFS_IP6_PLEN + 0] = 0; + pkt [Utils.OFS_IP6_PLEN + 1] = 8 + 16; + pkt [Utils.OFS_ICMP6_NGBADV_FLAGS] = 0x60; // Solicited, Override + // Assume that Utils.OFS_ICMP6_NGBADV_TARGET == Utils.OFS_ICMP6_NGBSOL_TARGET + int csum = Utils.checksum_icmpv6 (pkt, 0); + pkt [Utils.OFS_ICMP6_CSUM + 0] = (byte) (csum >> 8 ); + pkt [Utils.OFS_ICMP6_CSUM + 1] = (byte) (csum & 0xff); + DatagramPacket replypkt = new DatagramPacket (pkt, 0, 40 + 8 + 16, src); + super.send (replypkt); + + // + // TODO:OLD Replicate the message over the tunnel link + // + // We should attach a Source Link-Layer Address, but + // we cannot automatically trust the one provided remotely. + // Also, we want to detect if routes differ, and handle it. + // + // 0. if no entry in the ngb.cache + // then use 6bed4 server in ND, initiate ngb.sol to src.ll + // impl: use 6bed4-server lladdr, set highest metric + // 1. if metric (ngb.cache) < metric (src.ll) + // then retain ngb.cache, send Redirect to source + // 2. if metric (ngb.cache) > metric (src.ll) + // then retain ngb.cache, initiate ngb.sol to src.ll + // 3. if metric (ngb.cache) == metric (src.ll) + // then retain ngb.cache + // + //TODO// Handle Utils.ND_NEIGHBOR_SOLICIT (handle_4to6_nd) + return; + // + // Neighbor Advertisement may be in response to our peer-to-peer search + case Utils.ND_NEIGHBOR_ADVERT: + // + // Process Neighbor Advertisement coming in over 6bed4 + // First, make sure it is against an item in the ndqueue + // + // Validate the Neighbor Advertisement + if (pktlen < 64) { + // Packet too small to hold ICMPv6 Neighbor Advertisement + return; + } + if ((pkt [Utils.OFS_ICMP6_TYPE] != Utils.ND_NEIGHBOR_ADVERT) || (pkt [Utils.OFS_ICMP6_CODE] != 0)) { + // ICMPv6 Type or Code is wrong + return; + } + if ((ngbcache == null) || (!ngbcache.is6bed4 (pkt, Utils.OFS_IP6_SRC)) || (!ngbcache.is6bed4 (pkt, Utils.OFS_IP6_DST))) { + // Source or Destination IPv6 address is not a 6bed4 address + return; + } + if (Utils.memdiff_addr (pkt, Utils.OFS_IP6_SRC, pkt, Utils.OFS_ICMP6_NGBADV_TARGET)) { + // NgbAdv's Target Address does not match IPv6 source + return; + } + // + // Not checked here: IPv4/UDP source versus IPv6 source address (already done) + // + ngbcache.received_peer_direct_acknowledgement (pkt, Utils.OFS_ICMP6_NGBADV_TARGET, false); + return; + // + // Route Redirect messages are not supported in 6bed4 draft v01 + case Utils.ND_REDIRECT: + return; + } + } + + /* Forward an IPv6 packet, wrapped into UDP and IPv4 in the 6bed4 + * way, as a pure IPv6 packet over the tunnel interface. This is + * normally a simple copying operation. One exception exists for + * TCP ACK packets; these may be in response to a "playful" TCP SYN + * packet that was sent directly to the IPv4 recipient. This is a + * piggyback ride of the opportunistic connection efforts on the + * 3-way handshake for TCP, without a need to modify the packets! + * The only thing needed to make that work is to report success + * back to the Neighbor Cache, in cases when TCP ACK comes back in + * directly from the remote peer. + * + * Note that nothing is stopping an ACK packet that is meaningful + * to us from also being a SYN packet that is meaningful to the + * remote peer. We will simply do our thing and forward any ACK + * to the most direct route we can imagine -- which may well be + * the sender, _especially_ since we opened our 6bed4 port to the + * remote peer when sending our playful initial TCP packet. + * + * Observing the traffic on the network, this may well look like + * magic! All you see is plain TCP traffic crossing over directly + * if it is possible --and bouncing one or two packets through the + * tunnel otherwise-- and especially in the case where it can work + * directly it will be a surprise. Servers are therefore strongly + * encouraged to setup port forwarding for their 6bed4 addresses, + * or just open a hole in full cone NAT/firewall setups. This will + * mean zero delay and zero bypasses for 6bed4 on the majority of + * TCP connection initiations between 6bed4 peers! + */ + public void handle_4to6_plain (byte pkt [], int pktlen) + throws IOException { + // + // Check if the designated queue for this packet exists + int port = Utils.fetch_net16 (pkt, Utils.OFS_UDP6_DSTPORT); + //TODO// Handling for udp_clients: only for UDP-over-IPv6, only to the default address + BlockingQueue queue = udp_clients [port]; + if (queue == null) { + return; + } + // The buffer is already full -- drop the oldest element + if (queue.remainingCapacity () == 0) { + /* (void) */ queue.poll (); + } + // + // If this is a successful peering attempt, that is, a tcpack packet, report that back + // Note that the UDP/IPv4 source has already been validated against the IPv6 source + boolean tcpack = (pktlen >= 40 + 20) && (pkt [Utils.OFS_IP6_NXTHDR] == Utils.IPPROTO_TCP) && ((pkt [Utils.OFS_TCP6_FLAGS] & Utils.TCP_FLAG_ACK) != 0x00); + if (tcpack) { + if ((ngbcache != null) && ngbcache.is6bed4 (pkt, Utils.OFS_IP6_SRC)) { + ngbcache.received_peer_direct_acknowledgement (pkt, Utils.OFS_IP6_SRC, true); + } + } + // + // Enqueue the packet in the designated client queue + //TODO// Select whether this is UDP/TCP/??? for the shared address, or raw for unique addresses! + queue.offer (pkt); + } + + /** Handle a 6bed4 packet that is being stripped and passed on as + * an IPv6 packet. Returns true if the packet is suitable to be + * relayed as IPv6. + */ + public void handle_4to6 (DatagramPacket datagram) + throws IOException { + byte pkt [] = datagram.getData (); + int pktlen = datagram.getLength (); + + if (pktlen < 40) { + return; + } + if ((pkt [0] & (byte) 0xf0) != 0x60) { + return; + } + validate_originator (pkt, (InetSocketAddress) datagram.getSocketAddress ()); + if ((pkt [Utils.OFS_IP6_NXTHDR] == Utils.IPPROTO_ICMPV6) && (pkt [Utils.OFS_ICMP6_TYPE] >= Utils.ND_LOWEST) && (pkt [Utils.OFS_ICMP6_TYPE] <= Utils.ND_HIGHEST)) { + // + // Not Plain: Router Adv/Sol, Neighbor Adv/Sol, Redirect + handle_4to6_nd (pkt, pktlen, datagram.getSocketAddress ()); + return; + } else { + // + // Plain Unicast or Plain Multicast (both may enter) + handle_4to6_plain (pkt, pktlen); + return; + } + } + + public void validate_originator (byte pkt [], InetSocketAddress originator) + throws IOException { +/* TODO: validate originator address + if (tunserver.equals (originator)) { + return; + } + if (memdiff_halfaddr (pkt, Utils.OFS_IP6_SRC, router_linklocal, 0) && ((local_address == null) || memdiff_halfaddr (pkt, Utils.OFS_IP6_SRC, local_address, 8))) { + throw new IOException ("Incoming 6bed4 uses bad /64 prefix"); + } + int port = (pkt [Utils.OFS_IP6_SRC + 8] ^ 0x02) & 0xff; + port = (port | (pkt [Utils.OFS_IP6_SRC + 9] << 8) & 0xffff; + if (originator.getPort () != port) { + throw new IOException ("Incoming 6bed4 uses ") + } +*/ + } + + /* This routine passes IPv6 traffic from the tunnel interface on + * to the 6bed4 interface where it is wrapped into UDP and IPv4. + * The only concern for this is where to send it to -- should it + * be sent to the tunnel server, or directly to the peer? The + * Neighbor Cache is consulted for advise. + * + * A special flag exists to modify the behaviour of the response + * to this inquiry. This flag is used to signal that a first + * packet might be tried directly, which should be harmless if + * it fails and otherwise lead to optimistic connections if: + * 1. the packet will repeat upon failure, and + * 2. explicit acknowledgement can be reported to the cache + * This is the case with TCP connection setup; during a SYN, + * it is possible to be playful and try to send the first + * packet directly. A TCP ACK that returns directly from the + * sender indicates that return traffic is possible, which is + * then used to update the Neighbor Cache with positivism on + * the return route. + */ + public void handle_6to4_plain_unicast (byte pkt [], int pktlen) + throws IOException { + InetSocketAddress target; + if ((ngbcache != null) && ngbcache.is6bed4 (pkt, 24)) { + boolean tcpsyn = (pkt [Utils.OFS_IP6_NXTHDR] == Utils.IPPROTO_TCP) && ((pkt [Utils.OFS_TCP6_FLAGS] & Utils.TCP_FLAG_SYN) != 0x00); + target = ngbcache.lookup_neighbor (pkt, 24, tcpsyn); + } else { + target = tunserver; + } + } + + public void handle_6to4_nd (byte pkt [], int pktlen) + throws IOException { + throw new RuntimeException ("You should not be trying to send ND over the 6bed4 interface"); +/* TODO: Entire routine can probably be removed + switch (pkt [Utils.OFS_ICMP6_TYPE]) { + // + // Handle Router Solicitation by answering it with the local configuration + case Utils.ND_ROUTER_SOLICIT: + int ofs = Utils.OFS_ICMP6_TYPE; + pkt [ofs++] = Utils.ND_ROUTER_ADVERT; // type + pkt [ofs++] = 0; // code + ofs += 2; // checksum + pkt [ofs++] = 0; // hop limit -- unspecified + pkt [ofs++] = 0x18; // M=0, O=0, H=0, Prf=11=Low, Reserved=0 + pkt [ofs++] = 0x00; // Lifetime + pkt [ofs++] = 0x00; // (cont) + for (int i=0; i<8; i++) { + pkt [ofs++] = 0; // Reachable time, Retrans timer + } + pkt [ofs-6] = (byte) 0x80; // Reachable time := 32s + pkt [ofs-2] = 0x01; // Retrans timer := 0.25s + // Start of Prefix Option + pkt [ofs++] = Utils.ND_OPT_PREFIX_INFORMATION; + pkt [ofs++] = 4; // Option length = 4 * 8 bytes + pkt [ofs++] = (byte) 128; // Announce a /64 prefix (TODO: Temporarily /128) + pkt [ofs++] = (byte) 0x80; // Link-local, No autoconfig, tunnel does the work + for (int i=0; i<8; i++) { + pkt [ofs++] = (byte) 0xff; // Valid / Preferred Lifetime: Infinite + } + for (int i=0; i<4; i++) { + pkt [ofs++] = 0; // Reserved + } + Utils.memcp_address (pkt, ofs, local_address, 0); + ofs += 16; + // End of Prefix Option + Utils.memcp_address (pkt, Utils.OFS_IP6_DST, pkt, Utils.OFS_IP6_SRC); // dst:=src + Utils.memcp_address (pkt, Utils.OFS_IP6_SRC, local_address, 0); + //TODO// Send packet back to IPv6 downlink + return; + // + // Handle Router Advertisement by dropping it -- Android is not setup a router + case Utils.ND_ROUTER_ADVERT: + return; + // + // Neighbor Solicitation is not normally sent by the phone due to its /128 on 6bed4 + case Utils.ND_NEIGHBOR_SOLICIT: + return; + // + // Neighbor Advertisement is a response to a peer, and should be relayed + case Utils.ND_NEIGHBOR_ADVERT: + //TODO// Possibly arrange the peer's receiving address + handle_6to4_plain_unicast (pkt, pktlen); + return; + // Route Redirect messages are not supported in 6bed4 draft v01 + case Utils.ND_REDIRECT: + return; + } +*/ + } + + public void handle_6to4 (byte pkt [], int pktlen) + throws IOException { + if (pktlen < 41) { + return; + } + if ((pkt [0] & 0xf0) != 0x60) { + return; + } + if ((pkt [Utils.OFS_IP6_NXTHDR] == Utils.IPPROTO_ICMPV6) && (pkt [Utils.OFS_ICMP6_TYPE] >= Utils.ND_LOWEST) && (pkt [Utils.OFS_ICMP6_TYPE] <= Utils.ND_HIGHEST)) { + // + // Not Plain: Router Adv/Sol, Neighbor Adv/Sol, Redirect + handle_6to4_nd (pkt, pktlen); + } else if ((pkt [Utils.OFS_IP6_DST+0] != 0xff) && ((pkt [Utils.OFS_IP6_DST+8] & 0x01) == 0x00)) { + // + // Plain Unicast + pkt [Utils.OFS_IP6_HOPS]--; + if (pkt [Utils.OFS_IP6_HOPS] == 0) { + return; + } + handle_6to4_plain_unicast (pkt, pktlen); + } else { + // + // Plain Multicast + //TODO: Ignore Multicast for now... + } + } + + + /** The Worker inner class ensures that incoming traffic, and that + * includes 6bed4 tunnel management traffic, is processed as soon + * as it arrives. This means that applications need not actively + * read input to get it processed. Furthermore, this approach + * simplifies receiving all 6bed4 traffic on one IPv4/UDP socket + * and subsequently dispatching it to the various 6bed4 sockets + * connected to the same ServerNode, and possibly each applying + * their own idea of a socket timeout or other queueing disciplines. + * The expense (there always seems to be one) is having to queue + * received data between the ServerNode and the 6bed4 socket. + */ + private class Worker extends Thread { + + protected ServerNode uplink; + + /** The thread logic for the Worker inner class comes down + * to reading input from the IPv4/UDP socket, seeing if it + * needs local handling as a 6bed4 management packet, and + * otherwise shipping it off to the queue of a 6bed4 socket + * that is read when the user of the DatagramSocket6bed4 + * wants to grab its input. Note how this reflects the way + * that native UDP ports operate -- packets wait until they + * are picked up. + */ + public void run () { + DatagramPacket dgram4 = null; + while (!isInterrupted ()) { + try { + dgram4 = new DatagramPacket (new byte [1280 + 28], 1280 + 28); + uplink.receive (dgram4); + handle_4to6 (dgram4); + } catch (IOException ioe) { + ; //TODO// What to do here? Not sure... + } + } + } + + public Worker (ServerNode owner) { + this.uplink = owner; + } + + } + + /** The Maintainer inner class performs regular maintainence. + * One task is to acquire the router address by sending regular + * Router Solicitation messages to the 6bed4 tunnel server, + * and once it has an address it will regularly send keep-alives. + */ + private class Maintainer extends Thread { + + /* The time for the next scheduled maintenance: routersol or keepalive. + * The milliseconds are always 0 for maintenance tasks. + */ + private long maintenance_time_millis; + private int maintenance_time_cycle = 0; + private int maintenance_time_cycle_max = 30; + private boolean have_lladdr = false; + private DatagramPacket keepalive_packet = null; + private DatagramSocket uplink; + + /* Perform the initial Router Solicitation exchange with the public server. + */ + public void solicit_router () { + if (uplink != null) { + try { + DatagramPacket rtrsol = new DatagramPacket (Utils.router_solicitation, Utils.router_solicitation.length, tunserver); + uplink.send (rtrsol); + } catch (IOException ioe) { + /* Network is probably down, so don't + * throw new RuntimeException ("Network failure", ioe); + */ + } + } + } + + /* Send a KeepAlive packet to the public server. + * Note, ideally, we would set a low-enough TTL to never reach it; + * after all, the only goal is to open /local/ firewalls and NAT. + * Java however, is not capable of setting TTL on unicast sockets. + */ + public void keepalive () { + if ((keepalive_packet != null) && (uplink != null)) { + try { + uplink.send (keepalive_packet); + // Log.i (TAG, "Sent KeepAlive (empty UDP) to Tunnel Server"); + } catch (IOException ioe) { + ; /* Network is probably down; order reconnect to tunnel server */ + have_lladdr = false; + } + } + } + + /* Perform regular maintenance tasks: KeepAlive, and requesting a local address. + */ + public void regular_maintenance () { + if (!have_lladdr) { + solicit_router (); + maintenance_time_cycle <<= 1; + maintenance_time_cycle += 1; + if (maintenance_time_cycle > maintenance_time_cycle_max) { + maintenance_time_cycle = maintenance_time_cycle_max; + } + //TODO// syslog (LOG_INFO, "Sent Router Advertisement to Public 6bed4 Service, next attempt in %d seconds\n", maintenance_time_cycle); + // Log.i (TAG, "Sent Router Advertisement to Tunnel Server"); + } else { + //TODO// syslog (LOG_INFO, "Sending a KeepAlive message (empty UDP) to the 6bed4 Router\n"); + keepalive (); + if (have_lladdr) { + maintenance_time_cycle = maintenance_time_cycle_max; + } else { + maintenance_time_cycle = 1; + } + } + maintenance_time_millis = System.currentTimeMillis () + 1000 * (long) maintenance_time_cycle; + } + + /* Run the regular maintenance thread. This involves sending KeepAlives + * and possibly requesting a local address through Router Solicitation. + */ + public void run () { + try { + while (!isInterrupted ()) { + regular_maintenance (); + sleep (maintenance_time_millis - System.currentTimeMillis()); + } + } catch (InterruptedException ie) { + return; + } + } + + /* Tell the maintenance routine whether a local address has been setup. + * Until this is called, the maintenance will focus on getting one through + * regular Router Solicitation messages. It is possible to revert to this + * behaviour by setting the flag to false; this can be useful in case of + * changes, for instance resulting from an IPv4 address change. + */ + public void have_local_address (boolean new_setting) { + have_lladdr = new_setting; + if (have_lladdr) { + maintenance_time_cycle = maintenance_time_cycle_max; + maintenance_time_millis = System.currentTimeMillis () + 1000 * maintenance_time_cycle; + } + } + + /* See if a local address has been setup. + */ + public boolean have_local_address () { + return have_lladdr; + } + + /* Construct the Maintainer thread. + */ + public Maintainer (DatagramSocket uplink) { + this.uplink = uplink; + } + } + + +} + diff --git a/src/main/java/nl/openfortress/socket6bed4/Socket6bed4.java b/src/main/java/nl/openfortress/socket6bed4/Socket6bed4.java new file mode 100644 index 0000000..71b6b1f --- /dev/null +++ b/src/main/java/nl/openfortress/socket6bed4/Socket6bed4.java @@ -0,0 +1,14 @@ +package nl.openfortress.socket6bed4; + +import java.net.Socket; + + +public class Socket6bed4 +extends Socket { + + public Socket6bed4 () { + throw new UnsupportedOperationException ("Socket6bed4 cannot be implemented until a TCP implementation is written in Java"); + } + +} + diff --git a/src/main/java/nl/openfortress/socket6bed4/Utils.java b/src/main/java/nl/openfortress/socket6bed4/Utils.java new file mode 100644 index 0000000..e2d2007 --- /dev/null +++ b/src/main/java/nl/openfortress/socket6bed4/Utils.java @@ -0,0 +1,146 @@ +package nl.openfortress.socket6bed4; + + +class Utils { + + final static byte IPPROTO_ICMPV6 = 58; + final static byte IPPROTO_UDP = 17; + final static byte IPPROTO_TCP = 6; + + final static byte ND_ROUTER_SOLICIT = (byte) 133; + final static byte ND_ROUTER_ADVERT = (byte) 134; + final static byte ND_NEIGHBOR_SOLICIT = (byte) 135; + final static byte ND_NEIGHBOR_ADVERT = (byte) 136; + final static byte ND_REDIRECT = (byte) 137; + final static byte ND_LOWEST = (byte) 133; + final static byte ND_HIGHEST = (byte) 137; + + final static byte ND_OPT_PREFIX_INFORMATION = 3; + + final static int OFS_IP6_SRC = 8; + final static int OFS_IP6_DST = 24; + final static int OFS_IP6_PLEN = 4; + final static int OFS_IP6_NXTHDR = 6; + final static int OFS_IP6_HOPS = 7; + final static int OFS_IP6_PLOAD = 40; + + final static int OFS_UDP6_SRCPORT = 40 + 0; + final static int OFS_UDP6_DSTPORT = 40 + 2; + final static int OFS_UDP6_CSUM = 40 + 6; + final static int OFS_UDP6_PLEN = 40 + 4; + final static int OFS_UDP6_PLOAD = 40 + 8; + + final static int OFS_ICMP6_TYPE = 40 + 0; + final static int OFS_ICMP6_CODE = 40 + 1; + final static int OFS_ICMP6_CSUM = 40 + 2; + final static int OFS_ICMP6_DATA = 40 + 4; + + final static int OFS_ICMP6_NGBSOL_TARGET = 40 + 8; + final static int OFS_ICMP6_NGBADV_TARGET = 40 + 8; + final static int OFS_ICMP6_NGBADV_FLAGS = 40 + 4; + + final static int OFS_TCP6_FLAGS = 13; + final static int TCP_FLAG_SYN = 0x02; + final static int TCP_FLAG_ACK = 0x01; + + final static byte router_solicitation [] = { + // IPv6 header + 0x60, 0x00, 0x00, 0x00, + 16 / 256, 16 % 256, IPPROTO_ICMPV6, (byte) 255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // unspecd src + (byte) 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02, // all-rtr tgt + // ICMPv6 header: router solicitation + ND_ROUTER_SOLICIT, 0, 0x7a, (byte) 0xae, // Checksum courtesy of WireShark :) + // ICMPv6 body: reserved + 0, 0, 0, 0, + // ICMPv6 option: source link layer address 0x0001 (end-aligned) + 0x01, 0x01, 0, 0, 0, 0, 0x00, 0x01, + }; + + final static byte router_linklocal_address [] = { (byte)0xfe,(byte)0x80,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }; + + /* Retrieve an unsigned 16-bit value from a given index in a byte array and + * return it as an integer. + */ + public static int fetch_net16 (byte pkt [], int ofs16) { + int retval = ((int) pkt [ofs16]) << 8 & 0xff00; + retval = retval + (((int) pkt [ofs16+1]) & 0xff); + return retval; + } + + /* Calculate the ICMPv6 checksum field in a given IPv6 packet. + */ + public static int checksum_icmpv6 (byte pkt []) { + return checksum_icmpv6 (pkt, 0); + } + public static int checksum_icmpv6 (byte pkt [], int pktofs) { + int plen = fetch_net16 (pkt, pktofs + OFS_IP6_PLEN); + int nxth = ((int) pkt [pktofs + 6]) & 0xff; + // Pseudo header is IPv6 src/dst (included with packet) and plen/nxth and zeroes: + int csum = plen + nxth; + for (int i=8; i < OFS_IP6_PLOAD + plen; i += 2) { + if (i != OFS_ICMP6_CSUM) { + // Skip current checksum value + csum += fetch_net16 (pkt, pktofs + i); + } + } + // No need to treat a trailing single byte: ICMPv6 has no odd packet lengths + csum = (csum & 0xffff) + (csum >> 16); + csum = (csum & 0xffff) + (csum >> 16); + csum = csum ^ 0xffff; // 1's complement limited to 16 bits + return csum; + } + + /* Calculate the UDP checksum field in a given IPv6 packet. + */ + public static int checksum_udpv6 (byte pkt []) { + return checksum_udpv6 (pkt, 0); + } + public static int checksum_udpv6 (byte pkt [], int pktofs) { + int udplen = fetch_net16 (pkt, pktofs + OFS_UDP6_PLEN); + int nxth = ((int) pkt [pktofs + 6]) & 0xff; + // Pseudo header is IPv6 src/dst (included with packet) and plen/nxth and zeroes: + int csum = udplen + nxth; + for (int i=8; i < OFS_IP6_PLOAD + udplen; i++) { + if ((i & 0x0001) == 0x0000) { + if (i != OFS_UDP6_CSUM + 0) { + csum += (((int) pkt [pktofs + i]) << 8) & 0xff00; + } + } else { + if (i != OFS_UDP6_CSUM + 1) { + csum += ((int) pkt [pktofs + i]) & 0x00ff; + } + } + } + csum = (csum & 0xffff) + (csum >> 16); + csum = (csum & 0xffff) + (csum >> 16); + csum = csum ^ 0xffff; // 1's complement limited to 16 bits + return csum; + } + + public static void memcp_address (byte tgt [], int tgtofs, byte src [], int srcofs) { + for (int i=0; i<16; i++) { + tgt [tgtofs+i] = src [srcofs+i]; + } + } + + public static boolean memdiff_addr (byte one[], int oneofs, byte oth[], int othofs) { + for (int i=0; i<16; i++) { + if (one [oneofs + i] != oth [othofs + i]) { + return true; + } + } + return false; + } + + public static boolean memdiff_halfaddr (byte one[], int oneofs, byte oth[], int othofs) { + for (int i=0; i<8; i++) { + if (one [oneofs + i] != oth [othofs + i]) { + return true; + } + } + return false; + } + +} + -- 1.7.10.4