diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..bd884dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +<<<<<<< HEAD +# Main binaries created in *nix builds +/zerotier-one +/zerotier-idtool +/zerotier-cli +/zerotier-selftest +/zerotier + +# OS-created garbage files from various platforms +.DS_Store +.Apple* +Thumbs.db +@eaDir + +# Windows build droppings +/windows/ZeroTierOne.sdf +/windows/ZeroTierOne.v11.suo +/windows/x64 +/windows/Win32 +/windows/*/x64 +/windows/*/Win32 +/windows/ZeroTierOne/Release +/windows/ZeroTierOneService/obj +/windows/ZeroTierOneService/bin +/windows/Build +/windows/Debug +/windows/Release +/windows/WebUIWrapper/bin +/windows/WebUIWrapper/obj +/ext/installfiles/windows/ZeroTier One-SetupFiles +/ext/installfiles/windows/Prerequisites +/ext/installfiles/windows/*-cache +/ZeroTier One.msi +/windows/.vs +*.vcxproj.backup +/windows/TapDriver6/Win7Debug +/windows/TapDriver6/win7Release +/windows/*.db +/windows/*.opendb +enc_temp_folder +/windows/copyutil/bin +/windows/copyutil/obj + +# *nix/Mac build droppings +/build-* +/ZeroTierOneInstaller-* +/examples/docker/zerotier-one +/examples/docker/test-*.env +/world/mkworld +/world/*.c25519 +zt1-src.tar.gz + +# Miscellaneous temporaries, build files, etc. +*.log +*.opensdf +*.user +*.cache +*.obj +*.tlog +*.pid +*.pkg +*.o +/*.a +*.dylib +*.so +*.so.* +*.o-* +*.core +*.deb +*.rpm +*.autosave +*.tmp +.depend +node_modules +zt1_update_* +debian/files +debian/zerotier-one +debian/zerotier-one*.debhelper +debian/*.log +debian/zerotier-one.substvars +root-watcher/config.json + +# Java/Android/JNI build droppings +java/obj/ +java/libs/ +java/bin/ +java/classes/ +java/doc/ +java/build_win64/ +java/build_win32/ +/java/mac32_64/ +windows/WinUI/obj/ +windows/WinUI/bin/ +windows/ZeroTierOne/Debug/ +/ext/installfiles/windows/chocolatey/zerotier-one/*.nupkg + +# Miscellaneous mac/Xcode droppings +.DS_Store +.Trashes +*.swp +*~.nib +DerivedData/ +build/ +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 +*.xccheckout +xcuserdata/ diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..043ff00 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,73 @@ +## Primary Authors + + * ZeroTier Core and ZeroTier One virtual networking service
+ Adam Ierymenko / adam.ierymenko@zerotier.com + + * Java JNI Interface to enable Android application development, and Android app itself (code for that is elsewhere)
+ Grant Limberg / glimberg@gmail.com + + * ZeroTier SDK (formerly known as Network Containers)
+ Joseph Henry / joseph.henry@zerotier.com + +## Third Party Contributors + + * A number of fixes and improvements to the new controller, other stuff.
+ Kees Bos / https://github.com/keesbos/ + + * Debugging and testing, OpenWRT support fixes.
+ Moritz Warning / moritzwarning@web.de + + * Debian GNU/Linux packaging, manual pages, and license compliance edits.
+ Ben Finney + + * Several others made smaller contributions, which GitHub tracks here:
+ https://github.com/zerotier/ZeroTierOne/graphs/contributors/ + +## Third-Party Code + +ZeroTier includes the following third party code, either in ext/ or incorporated into the ZeroTier core. + + * LZ4 compression algorithm by Yann Collet + + * Files: node/Packet.cpp (bundled within anonymous namespace) + * Home page: http://code.google.com/p/lz4/ + * License grant: BSD 2-clause + + * http-parser by Joyent, Inc. (many authors) + + * Files: ext/http-parser/* + * Home page: https://github.com/joyent/http-parser/ + * License grant: MIT/Expat + + * C++11 json (nlohmann/json) by Niels Lohmann + + * Files: ext/json/* + * Home page: https://github.com/nlohmann/json + * License grant: MIT + + * TunTapOSX by Mattias Nissler + + * Files: ext/tap-mac/tuntap/* + * Home page: http://tuntaposx.sourceforge.net/ + * License grant: BSD attribution no-endorsement + * ZeroTier Modifications: change interface name to zt#, increase max MTU, increase max devices + + * tap-windows6 by the OpenVPN project + + * Files: windows/TapDriver6/* + * Home page: https://github.com/OpenVPN/tap-windows6/ + * License grant: GNU GPL v2 + * ZeroTier Modifications: change name of driver to ZeroTier, add ioctl() to get L2 multicast memberships (source is in ext/ and modifications inherit GPL) + + * Salsa20 stream cipher, Curve25519 elliptic curve cipher, Ed25519 digital signature algorithm, and Poly1305 MAC algorithm, all by Daniel J. Bernstein + + * Files: node/Salsa20.* node/C25519.* node/Poly1305.* + * Home page: http://cr.yp.to/ + * License grant: public domain + * ZeroTier Modifications: slight cryptographically-irrelevant modifications for inclusion into ZeroTier core + + * MiniUPNPC and libnatpmp by Thomas Bernard + + * Files: ext/libnatpmp/* ext/miniupnpc/* + * Home page: http://miniupnp.free.fr/ + * License grant: BSD attribution no-endorsement diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..23d42df --- /dev/null +++ b/COPYING @@ -0,0 +1,17 @@ +ZeroTier One, an endpoint server for the ZeroTier virtual network layer. +Copyright © 2011–2016 ZeroTier, Inc. + +ZeroTier One 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. + +See the file ‘LICENSE.GPL-3’ for the text of the GNU GPL version 3. +If that file is not present, see . + +.. + Local variables: + coding: utf-8 + mode: text + End: + vim: fileencoding=utf-8 filetype=text : diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..74c8624 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,82 @@ +#!/usr/bin/env groovy + +node('master') { + def changelog = getChangeLog currentBuild + + slackSend "Building ${env.JOB_NAME} #${env.BUILD_NUMBER} \n Change Log: \n ${changelog}" +} + +parallel 'centos7': { + node('centos7') { + try { + checkout scm + + stage('Build Centos 7') { + sh 'make -f make-linux.mk' + } + } + catch (err) { + currentBuild.result = "FAILURE" + slackSend color: '#ff0000', message: "${env.JOB_NAME} broken on Centos 7 (<${env.BUILD_URL}|Open>)" + + throw err + } + } +}, 'android-ndk': { + node('android-ndk') { + try { + checkout scm + + stage('Build Android NDK') { + sh "/android/android-ndk-r13b/ndk-build -C $WORKSPACE/java ZT1=${WORKSPACE}" + } + } + catch (err) { + currentBuild.result = "FAILURE" + slackSend color: '#ff0000', message: "${env.JOB_NAME} broken on Android NDK (<${env.BUILD_URL}|Open>)" + + throw err + } + } +}, 'macOS': { + node('macOS') { + try { + checkout scm + + stage('Build macOS') { + sh 'make -f make-mac.mk' + } + + stage('Build macOS UI') { + sh 'cd macui && xcodebuild -target "ZeroTier One" -configuration Debug' + } + } + catch (err) { + currentBuild.result = "FAILURE" + slackSend color: '#ff0000', message: "${env.JOB_NAME} broken on macOS (<${env.BUILD_URL}|Open>)" + + throw err + } + } +}, 'windows': { + node('windows') { + try { + checkout scm + + stage('Build Windows') { + bat '''CALL "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat" amd64 +git clean -dfx +msbuild windows\\ZeroTierOne.sln +''' + } + } + catch (err) { + currentBuild.result = "FAILURE" + slackSend color: '#ff0000', message: "${env.JOB_NAME} broken on Windows (<${env.BUILD_URL}|Open>)" + + throw err + } + } +} + +slackSend color: "#00ff00", message: "${env.JOB_NAME} #${env.BUILD_NUMBER} Complete (<${env.BUILD_URL}|Show More...>)" diff --git a/LICENSE.GPL-2 b/LICENSE.GPL-2 new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE.GPL-2 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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. diff --git a/LICENSE.GPL-3 b/LICENSE.GPL-3 new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE.GPL-3 @@ -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/Makefile b/Makefile new file mode 100644 index 0000000..9511862 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +# Common makefile -- loads make rules for each platform + +OSTYPE=$(shell uname -s) + +ifeq ($(OSTYPE),Darwin) + include make-mac.mk +endif + +ifeq ($(OSTYPE),Linux) + include make-linux.mk +endif + +ifeq ($(OSTYPE),FreeBSD) + CC=clang + CXX=clang++ + ZT_BUILD_PLATFORM=7 + include make-bsd.mk +endif +ifeq ($(OSTYPE),OpenBSD) + CC=egcc + CXX=eg++ + ZT_BUILD_PLATFORM=9 + include make-bsd.mk +endif diff --git a/OFFICIAL-RELEASE-STEPS.md b/OFFICIAL-RELEASE-STEPS.md new file mode 100644 index 0000000..d0f42e3 --- /dev/null +++ b/OFFICIAL-RELEASE-STEPS.md @@ -0,0 +1,64 @@ +ZeroTier Official Release Steps +====== + +This is mostly for ZeroTier internal use, but others who want to do builds might find it helpful. + +Note: Many of these steps will require GPG and other signing keys that are kept in cold storage and must be mounted. + +# Bumping the Version and Preparing Installers + +The version must be incremented in all of the following files: + + /version.h + /zerotier-one.spec + /debian/changelog + /ext/installfiles/mac/ZeroTier One.pkgproj + /ext/installfiles/windows/chocolatey/zerotier-one.nuspec + /ext/installfiles/windows/ZeroTier One.aip + /windows/WinUI/AboutView.xaml + +The final .AIP file can only be edited on Windows with [Advanced Installer Enterprise](http://www.advancedinstaller.com/). In addition to incrementing the version be sure that a new product code is generated. (The "upgrade code" GUID on the other hand must never change.) + +# Building for Supported Platforms + +## Macintosh + +Mac's easy. Just type: + + make official + +You will need [Packages](http://s.sudre.free.fr/Software/Packages/about.html) and our release signing key in the keychain. + +## Linux + +Mount the GPG key for *contact@zerotier.com* and then on an x86_64 box with a recent version of Docker and an Internet connection run: + + make distclean + cd linux-build-farm + ./build.sh + +This will build i386 and x86_64 packages. Now ssh into our build Raspberry Pi and type `make debian` there to build the Raspbian armhf package. Copy it to `debian-jessie/` inside `linux-build-farm` so that it will be included in the repositories we generate. Now generate the YUM and APT repos: + + rm -rf ~/.aptly* + rm -rf /tmp/zt-rpm-repo + ./make-apt-repos.sh + ./make-rpm-repos.sh + +This will require the passphrase for *contact@zerotier.com*. + +The contents of ~/.aptly/public must be published as `debian/` on `download.zerotier.com`. The contents of /tmp/zt-rpm-repo are published as `redhat/` on same. + +## Windows + +First load the Visual Studio solution and rebuild the UI and ZeroTier One in both x64 and i386 `Release` mode. Then load [Advanced Installer Enterprise](http://www.advancedinstaller.com/), check that the version is correct, and build. The build will fail if any build artifacts are missing, and Windows must have our product singing key (from DigiCert) available to sign the resulting MSI file. The MSI must then be tested on at least a few different CLEAN Windows VMs to ensure that the installer is valid and properly signed. + +*After the MSI is published to download.zerotier.com in the proper RELEASE/#.#.#/dist subfolder for its version* the Chocolatey package must be rebuilt and published. Open a command prompt, change to `ext/installfiles/windows/chocolatey`, and type `choco pack`. Then use `choco push` to push it to Chocolatey (API key required). + + choco pack + choco push zerotier-one.#.#.#.nupkg -s https://chocolatey.org/ + +Note that this does not cover rebuilding the drivers or their containing MSI projects, as this is typically not necessary and they are shipped in binary form in the repository for convenience. + +## iOS, Android + +... no docs here yet since this is done entirely out of band with regular installs. diff --git a/README.md b/README.md new file mode 100644 index 0000000..47bfc87 --- /dev/null +++ b/README.md @@ -0,0 +1,125 @@ +ZeroTier - A Planetary Ethernet Switch +====== + +ZeroTier is an enterprise Ethernet switch for planet Earth. + +It erases the LAN/WAN distinction and makes VPNs, tunnels, proxies, and other kludges arising from the inflexible nature of physical networks obsolete. Everything is encrypted end-to-end and traffic takes the most direct (peer to peer) path available. + +Visit [ZeroTier's site](https://www.zerotier.com/) for more information and [pre-built binary packages](https://www.zerotier.com/download.shtml). Apps for Android and iOS are available for free in the Google Play and Apple app stores. + +### Getting Started + +ZeroTier's basic operation is easy to understand. Devices have 10-digit *ZeroTier addresses* like `89e92ceee5` and networks have 16-digit network IDs like `8056c2e21c000001`. All it takes for a device to join a network is its 16-digit ID, and all it takes for a network to authorize a device is its 10-digit address. Everything else is automatic. + +A "device" in our terminology is any "unit of compute" capable of talking to a network: desktops, laptops, phones, servers, VMs/VPSes, containers, and even user-space applications via our [SDK](https://github.com/zerotier/ZeroTierSDK). + +For testing purposes we provide a public virtual network called *Earth* with network ID `8056c2e21c000001`. You can join it with: + + sudo zerotier-cli join 8056c2e21c000001 + +Now wait about 30 seconds and check your system with `ip addr list` or `ifconfig`. You'll see a new interface whose name starts with *zt* and it should quickly get an IPv4 and an IPv6 address. Once you see it get an IP, try pinging `earth.zerotier.net` at `29.209.112.93`. If you've joined Earth from more than one system, try pinging your other machine. If you don't want to belong to a giant Ethernet party line anymore, just type: + + sudo zerotier-cli leave 8056c2e21c000001 + +The *zt* interface will disappear. You're no longer on the network. + +To create networks of your own, you'll need a network controller. ZeroTier One (for desktops and servers) includes controller functionality in its default build that can be configured via its JSON API (see [README.md in controller/](controller/)). ZeroTier provides a hosted solution with a nice web UI and SaaS add-ons at [my.zerotier.com](https://my.zerotier.com/). Basic controller functionality is free for up to 100 devices. + +### Project Layout + + - `artwork/`: icons, logos, etc. + - `attic/`: old stuff and experimental code that we want to keep around for reference. + - `controller/`: the reference network controller implementation, which is built and included by default on desktop and server build targets. + - `debian/`: files for building Debian packages on Linux. + - `doc/`: manual pages and other documentation. + - `ext/`: third party libraries, binaries that we ship for convenience on some platforms (Mac and Windows), and installation support files. + - `include/`: include files for the ZeroTier core. + - `java/`: a JNI wrapper used with our Android mobile app. (The whole Android app is not open source but may be made so in the future.) + - `macui/`: a Macintosh menu-bar app for controlling ZeroTier One, written in Objective C. + - `node/`: the ZeroTier virtual Ethernet switch core, which is designed to be entirely separate from the rest of the code and able to be built as a stand-alone OS-independent library. Note to developers: do not use C++11 features in here, since we want this to build on old embedded platforms that lack C++11 support. C++11 can be used elsewhere. + - `osdep/`: code to support and integrate with OSes, including platform-specific stuff only built for certain targets. + - `service/`: the ZeroTier One service, which wraps the ZeroTier core and provides VPN-like connectivity to virtual networks for desktops, laptops, servers, VMs, and containers. + - `tcp-proxy/`: TCP proxy code run by ZeroTier, Inc. to provide TCP fallback (this will die soon!). + - `windows/`: Visual Studio solution files, Windows service code for ZeroTier One, and the Windows task bar app UI. + +The base path contains the ZeroTier One service main entry point (`one.cpp`), self test code, makefiles, etc. + +### Build and Platform Notes + +To build on Mac and Linux just type `make`. On FreeBSD and OpenBSD `gmake` (GNU make) is required and can be installed from packages or ports. For Windows there is a Visual Studio solution in `windows/'. + + - **Mac** + - Xcode command line tools for OSX 10.7 or newer are required. + - Tap device driver kext source is in `ext/tap-mac` and a signed pre-built binary can be found in `ext/bin/tap-mac`. You should not need to build it yourself. It's a fork of [tuntaposx](http://tuntaposx.sourceforge.net) with device names changed to `zt#`, support for a larger MTU, and tun functionality removed. + - **Linux** + - The minimum compiler versions required are GCC/G++ 4.9.3 or CLANG/CLANG++ 3.4.2. + - Linux makefiles automatically detect and prefer clang/clang++ if present as it produces smaller and slightly faster binaries in most cases. You can override by supplying CC and CXX variables on the make command line. + - CentOS 7 ships with a version of GCC/G++ that is too old, but a new enough version of CLANG can be found in the *epel* repositories. Type `yum install epel-release` and then `yum install clang` to build there. + - **Windows** + - Windows 7 or newer (and equivalent server versions) are supported. This *may* work on Vista but you're on your own there. Windows XP is not supported since it lacks many important network API functions. + - We build with Visual Studio 2015. Older versions may not work with the solution file and project files we ship and may not have new enough C++11 support. + - Pre-built signed Windows drivers are included in `ext/bin/tap-windows-ndis6`. The MSI files found there will install them on 32-bit and 64-bit systems. (These are included in our multi-architecture installer as chained MSIs.) + - Windows builds are more painful in general than other platforms and are for the adventurous. + - **FreeBSD** + - Tested most recently on FreeBSD-11. Older versions may work but we're not sure. + - GCC/G++ 4.9 and gmake are required. These can be installed from packages or ports. Type `gmake` to build. + - **OpenBSD** + - There is a limit of four network memberships on OpenBSD as there are only four tap devices (`/dev/tap0` through `/dev/tap3`). We're not sure if this can be increased. + - OpenBSD lacks `getifmaddrs` (or any equivalent method) to get interface multicast memberships. As a result multicast will only work on OpenBSD for ARP and NDP (IP/MAC lookup) and not for other purposes. + - Only tested on OpenBSD 6.0. Older versions may not work. + - GCC/G++ 4.9 and gmake are required and can be installed using `pkg_add` or from ports. They get installed in `/usr/local/bin` as `egcc` and `eg++` and our makefile is pre-configured to use them on OpenBSD. + +Typing `make selftest` will build a *zerotier-selftest* binary which unit tests various internals and reports on a few aspects of the build environment. It's a good idea to try this on novel platforms or architectures. + +### Running + +Running *zerotier-one* with -h will show help. + +On Linux and BSD you can start the service with: + + sudo ./zerotier-one -d + +A home folder for your system will automatically be created. + +The service is controlled via the JSON API, which by default is available at 127.0.0.1 port 9993. We include a *zerotier-cli* command line utility to make API calls for standard things like joining and leaving networks. The *authtoken.secret* file in the home folder contains the secret token for accessing this API. See README.md in [service/](service/) for API documentation. + +Here's where home folders live (by default) on each OS: + + * **Linux**: `/var/lib/zerotier-one` + * **FreeBSD** / **OpenBSD**: `/var/db/zerotier-one` + * **Mac**: `/Library/Application Support/ZeroTier/One` + * **Windows**: `\ProgramData\ZeroTier\One` (That's for Windows 7. The base 'shared app data' folder might be different on different Windows versions.) + +Running ZeroTier One on a Mac is the same, but OSX requires a kernel extension. We ship a signed binary build of the ZeroTier tap device driver, which can be installed on Mac with: + + sudo make install-mac-tap + +This will create the home folder for Mac, place *tap.kext* there, and set its modes correctly to enable ZeroTier One to manage it with *kextload* and *kextunload*. + +### Troubleshooting + +For most users, it just works. + +If you are running a local system firewall, we recommend adding a rule permitting UDP port 9993 inbound and outbound. If you installed binaries for Windows this should be done automatically. Other platforms might require manual editing of local firewall rules depending on your configuration. + +The Mac firewall can be found under "Security" in System Preferences. Linux has a variety of firewall configuration systems and tools. If you're using Ubuntu's *ufw*, you can do this: + + sudo ufw allow 9993/udp + +On CentOS check `/etc/sysconfig/iptables` for IPTables rules. For other distributions consult your distribution's documentation. You'll also have to check the UIs or documentation for commercial third party firewall applications like Little Snitch (Mac), McAfee Firewall Enterprise (Windows), etc. if you are running any of those. Some corporate environments might have centrally managed firewall software, so you might also have to contact IT. + +ZeroTier One peers will automatically locate each other and communicate directly over a local wired LAN *if UDP port 9993 inbound is open*. If that port is filtered, they won't be able to see each others' LAN announcement packets. If you're experiencing poor performance between devices on the same physical network, check their firewall settings. Without LAN auto-location peers must attempt "loopback" NAT traversal, which sometimes fails and in any case requires that every packet traverse your external router twice. + +Users behind certain types of firewalls and "symmetric" NAT devices may not able able to connect to external peers directly at all. ZeroTier has limited support for port prediction and will *attempt* to traverse symmetric NATs, but this doesn't always work. If P2P connectivity fails you'll be bouncing UDP packets off our relay servers resulting in slower performance. Some NAT router(s) have a configurable NAT mode, and setting this to "full cone" will eliminate this problem. If you do this you may also see a magical improvement for things like VoIP phones, Skype, BitTorrent, WebRTC, certain games, etc., since all of these use NAT traversal techniques similar to ours. + +If you're interested, there's a [technical deep dive about NAT traversal on our blog](https://www.zerotier.com/blog/?p=226). A troubleshooting tool to help you diagnose NAT issues is planned for the future as are uPnP/IGD/NAT-PMP and IPv6 transport. + +If a firewall between you and the Internet blocks ZeroTier's UDP traffic, you will fall back to last-resort TCP tunneling to rootservers over port 443 (https impersonation). This will work almost anywhere but is *very slow* compared to UDP or direct peer to peer connectivity. + +### Contributing + +Please make pull requests against the `dev` branch. The `master` branch is release, and `edge` is for unstable and work in progress changes and is not likely to work. + +### License + +The ZeroTier source code is open source and is licensed under the GNU GPL v3 (not LGPL). If you'd like to embed it in a closed-source commercial product or appliance, please e-mail [contact@zerotier.com](mailto:contact@zerotier.com) to discuss commercial licensing. Otherwise it can be used for free. diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md new file mode 100644 index 0000000..195e888 --- /dev/null +++ b/RELEASE-NOTES.md @@ -0,0 +1,138 @@ +ZeroTier Release Notes +====== + +# 2017-04-20 -- Version 1.2.4 + + * Managed routes are now only bifurcated for the default route. This is a change in behavior, though few people will probably notice. Bifurcating all managed routes was causing more trouble than it was worth for most users. + * Up to 2X crypto speedup on x86-64 (except Windows, which will take some porting) and 32-bit ARM platforms due to integration of fast assembly language implementations of Salsa20/12 from the [supercop](http://bench.cr.yp.to/supercop.html) code base. These were written by Daniel J. Bernstein and are in the public domain. My Macbook Pro (Core i5 2.8ghz) now does almost 1.5GiB/sec Salsa20/12 per core and a Raspberry Pi got a 2X boost. 64-bit ARM support and Windows support will take some work but should not be too hard. + * Refactored code that manages credentials to greatly reduce memory use in most cases. This may also result in a small performance improvement. + * Reworked and simplified path selection and priority logic to fix path instability and dead path persistence edge cases. There have been some sporadic reports of persistent path instabilities and dead paths hanging around that take minutes to resolve. These have proven difficult to reproduce in house, but hopefully this will fix them. In any case it seems to speed up path establishment in our tests and it makes the code simpler and more readable. + * Eliminated some unused cruft from the code around path management and in the peer class. + * Fixed an issue causing build problems on some MIPS architecture systems. + * Fixed Windows forgetting routes on sleep/wake or in some other circumstances. (GitHub issue #465) + +# 2017-03-17 -- Version 1.2.2 + + * A bug causing unreliable multicast propagation (GitHub issue #461). + * A crash in ARM binaries due to a build chain and flags problem. + * A bug in the network controller preventing members from being listed (GitHub issue #460). + +# 2017-03-14 -- Version 1.2.0 + +Version 1.2.0 is a major milestone release representing almost nine months of work. It includes our rules engine for distributed network packet filtering and security monitoring, federated roots, and many other architectural and UI improvements and bug fixes. + +## New Features in 1.2.0 + +### The ZeroTier Rules Engine + +The largest new feature in 1.2.0, and the product of many months of work, is our advanced network rules engine. With this release we achieve traffic control, security monitoring, and micro-segmentation capability on par with many enterprise SDN solutions designed for use in advanced data centers and corporate networks. + +Rules allow you to filter packets on your network and vector traffic to security observers. Security observation can be performed in-band using REDIRECT or out of band using TEE. + +Tags and capabilites provide advanced methods for implementing fine grained permission structures and micro-segmentation schemes without bloating the size and complexity of your rules table. + +See the [rules engine announcement blog post](https://www.zerotier.com/blog/?p=927) for an in-depth discussion of theory and implementation. The [manual](https://www.zerotier.com/manual.shtml) contains detailed information on rule, tag, and capability use, and the `rule-compiler/` subfolder of the ZeroTier source tree contains a JavaScript function to compile rules in our human-readable rule definition language into rules suitable for import into a network controller. (ZeroTier Central uses this same script to compile rules on [my.zerotier.com](https://my.zerotier.com/).) + +### Root Server Federation + +It's now possible to create your own root servers and add them to the root server pool on your nodes. This is done by creating what's called a "moon," which is a signed enumeration of root servers and their stable points on the network. Refer to the [manual](https://www.zerotier.com/manual.shtml) for instructions. + +Federated roots achieve a number of things: + + * You can deploy your own infrastructure to reduce dependency on ours. + * You can deploy roots *inside your LAN* to ensure that network connectivity inside your facility still works if the Internet goes down. This is the first step toward making ZeroTier viable as an in-house SDN solution. + * Roots can be deployed inside national boundaries for countries with data residency laws or "great firewalls." (As of 1.2.0 there is still no way to force all traffic to use these roots, but that will be easy to do in a later version.) + * Last but not least this makes ZeroTier somewhat less centralized by eliminating any hard dependency on ZeroTier, Inc.'s infrastructure. + +Our roots will of course remain and continue to provide zero-configuration instant-on deployment, a secure global authority for identities, and free traffic relaying for those who can't establish peer to peer connections. + +### Local Configuration + +An element of our design philosophy is "features are bugs." This isn't an absolute dogma but more of a guiding principle. We try as hard as we can to avoid adding features, especially "knobs" that must be tweaked by a user. + +As of 1.2.0 we've decided that certain knobs are unavoidable, and so there is now a `local.conf` file that can be used to configure them. See the ZeroTier One documentation for these. They include: + + * Blacklisting interfaces you want to make sure ZeroTier doesn't use for network traffic, such as VPNs, slow links, or backplanes designated for only certain kinds of traffic. + * Turning uPnP/NAT-PMP on or off. + * Configuring software updates on Windows and Mac platforms. + * Defining trusted paths (the old trusted paths file is now deprecated) + * Setting the ZeroTier main port so it doesn't have to be changed on the command line, which is very inconvenient in many cases. + +### Improved In-Band Software Updates + +A good software update system for Windows and Mac clients has been a missing feature in previous versions. It does exist but we've been shy about using it so far due to its fragility in some environments. + +We've greatly improved this mechanism in 1.2.0. Not only does it now do a better job of actually invoking the update, but it also transfers updates in-band using the ZeroTier protocol. This means it can work in environments that do not allows http/https traffic or that force it through proxies. There's also now an update channel setting: `beta` or `release` (the default). + +Software updates are authenticated three ways: + + 1. ZeroTier's own signing key is used to sign all updates and this signature is checked prior to installation. ZeroTier, Inc.'s signatures are performed on an air-gapped machine. + + 2. Updates for Mac and Windows are signed using Apple and Microsoft (DigiCert EV) keys and will not install unless these signatures are also valid. + + 3. The new in-band update mechanism also authenticates the source of the update via ZeroTier's built-in security features. This provides transport security, while 1 and 2 provide security of the update at rest. + +Updates are now configurable via `local.conf`. There are three options: `disable`, `download`, and `apply`. The third (apply) is the default for official builds on Windows and Mac, making updates happen silently and automatically as they do for popular browsers like Chrome and Firefox. Updates are disabled by default on Linux and other Unix-type systems as these are typically updated through package managers. + +### Path Link Quality Awareness + +Version 1.2.0 is now aware of the link quality of direct paths with other 1.2.0 nodes. This information isn't used yet but is visible through the JSON API. (Quality always shows as 100% with pre-1.2.0 nodes.) Quality is measured passively with no additional overhead using a counter based packet loss detection algorithm. + +This information is visible from the command line via `listpeers`: + + 200 listpeers XXXXXXXXXX 199.XXX.XXX.XXX/9993;10574;15250;1.00 48 1.2.0 LEAF + 200 listpeers XXXXXXXXXX 195.XXX.XXX.XXX/45584;467;7608;0.44 290 1.2.0 LEAF + +The first peer's path is at 100% (1.00), while the second peer's path is suffering quite a bit of packet loss (0.44). + +Link quality awareness is a precursor to intelligent multi-path and QoS support, which will in future versions bring us to feature parity with SD-WAN products like Cisco iWAN. + +### Security Improvements + +Version 1.2.0 adds anti-DOS (denial of service) rate limits and other hardening for improved resiliency against a number of denial of service attack scenarios. + +It also adds a mechanism for instantaneous credential revocation. This can be used to revoke certificates of membership instantly to kick a node off a network (for private networks) and also to revoke capabilities and tags. The new controller sends revocations by default when a peer is de-authorized. + +Revocations propagate using a "rumor mill" peer to peer algorithm. This means that a controller need only successfully send a revocation to at least one member of a network with connections to other active members. At this point the revocation will flood through the network peer to peer very quickly. This helps make revocations more robust in the face of poor connectivity with the controller or attempts to incapacitate the controller with denial of service attacks, as well as making revocations faster on huge networks. + +### Windows and Macintosh UI Improvements (ZeroTier One) + +The Mac has a whole new UI built natively in Objective-C. It provides a pulldown similar in appearance and operation to the Mac WiFi task bar menu. + +The Windows UI has also been improved and now provides a task bar icon that can be right-clicked to manage networks. Both now expose managed route and IP permissions, allowing nodes to easily opt in to full tunnel operation if you have a router configured on your network. + +### Ad-Hoc Networks + +A special kind of public network called an ad-hoc network may be accessed by joining a network ID with the format: + + ffSSSSEEEE000000 + | | | | + | | | Reserved for future use, must be 0 + | | End of port range (hex) + | Start of port range (hex) + Reserved ZeroTier address prefix indicating a controller-less network + +Ad-hoc networks are public (no access control) networks that have no network controller. Instead their configuration and other credentials are generated locally. Ad-hoc networks permit only IPv6 UDP and TCP unicast traffic (no multicast or broadcast) using 6plane format NDP-emulated IPv6 addresses. In addition an ad-hoc network ID encodes an IP port range. UDP packets and TCP SYN (connection open) packets are only allowed to desintation ports within the encoded range. + +For example `ff00160016000000` is an ad-hoc network allowing only SSH, while `ff0000ffff000000` is an ad-hoc network allowing any UDP or TCP port. + +Keep in mind that these networks are public and anyone in the entire world can join them. Care must be taken to avoid exposing vulnerable services or sharing unwanted files or other resources. + +### Network Controller (Partial) Rewrite + +The network controller has been largely rewritten to use a simple in-filesystem JSON data store in place of SQLite, and it is now included by default in all Windows, Mac, Linux, and BSD builds. This means any desktop or server node running ZeroTier One can now be a controller with no recompilation needed. + +If you have data in an old SQLite3 controller we've included a NodeJS script in `controller/migrate-sqlite` to migrate data to the new format. If you don't migrate, members will start getting `NOT_FOUND` when they attempt to query for updates. + +## Major Bug Fixes in 1.2.0 + + * **The Windows HyperV 100% CPU bug is FINALLY DEAD**: This long-running problem turns out to have been an issue with Windows itself, but one we were triggering by placing invalid data into the Windows registry. Microsoft is aware of the issue but we've also fixed the triggering problem on our side. ZeroTier should now co-exist quite well with HyperV and should now be able to be bridged with a HyperV virtual switch. + * **Segmenation faults on musl-libc based Linux systems**: Alpine Linux and some embedded Linux systems that use musl libc (a minimal libc) experienced segmentation faults. These were due to a smaller default stack size. A work-around that sets the stack size for new threads has been added. + * **Windows firewall blocks local JSON API**: On some Windows systems the firewall likes to block 127.0.0.1:9993 for mysterious reasons. This is now fixed in the installer via the addition of another firewall exemption rule. + * **UI crash on embedded Windows due to missing fonts**: The MSI installer now ships fonts and will install them if they are not present, so this should be fixed. + +## Other Improvements in 1.2.0 + + * **Improved dead path detection**: ZeroTier is now more aggressive about expiring paths that do not seem to be active. If a path seems marginal it is re-confirmed before re-use. + * **Minor performance improvements**: We've reduced unnecessary memcpy's and made a few other performance improvements in the core. + * **Linux static binaries**: For our official packages (the ones in the download.zerotier.com apt and yum repositories) we now build Linux binaries with static linking. Hopefully this will stop all the bug reports relating to library inconsistencies, as well as allowing our deb packages to run on a wider variety of Debian-based distributions. (There are far too many of these to support officially!) The overhead for this is very small, especially since we built our static versions against musl-libc. Distribution maintainers are of course free to build dynamically linked versions for inclusion into distributions; this only affects our official binaries. diff --git a/artwork/AppIcon.png b/artwork/AppIcon.png new file mode 100644 index 0000000..b96076b Binary files /dev/null and b/artwork/AppIcon.png differ diff --git a/artwork/AppIcon@2x.png b/artwork/AppIcon@2x.png new file mode 100644 index 0000000..6f1952e Binary files /dev/null and b/artwork/AppIcon@2x.png differ diff --git a/artwork/AppIcon@3x.png b/artwork/AppIcon@3x.png new file mode 100644 index 0000000..b32d323 Binary files /dev/null and b/artwork/AppIcon@3x.png differ diff --git a/artwork/AppIcon_29x29.png b/artwork/AppIcon_29x29.png new file mode 100644 index 0000000..762af5c Binary files /dev/null and b/artwork/AppIcon_29x29.png differ diff --git a/artwork/AppIcon_40x40.png b/artwork/AppIcon_40x40.png new file mode 100644 index 0000000..b0a44c4 Binary files /dev/null and b/artwork/AppIcon_40x40.png differ diff --git a/artwork/AppIcon_58x58.png b/artwork/AppIcon_58x58.png new file mode 100644 index 0000000..6778218 Binary files /dev/null and b/artwork/AppIcon_58x58.png differ diff --git a/artwork/AppIcon_80x80.png b/artwork/AppIcon_80x80.png new file mode 100644 index 0000000..3d630d4 Binary files /dev/null and b/artwork/AppIcon_80x80.png differ diff --git a/artwork/AppIcon_87x87.png b/artwork/AppIcon_87x87.png new file mode 100644 index 0000000..cd864eb Binary files /dev/null and b/artwork/AppIcon_87x87.png differ diff --git a/artwork/AppIcon_iPad@2x.png b/artwork/AppIcon_iPad@2x.png new file mode 100644 index 0000000..0d3d7a7 Binary files /dev/null and b/artwork/AppIcon_iPad@2x.png differ diff --git a/artwork/AppIcon_iPadPro@2x.png b/artwork/AppIcon_iPadPro@2x.png new file mode 100644 index 0000000..9a8cfcf Binary files /dev/null and b/artwork/AppIcon_iPadPro@2x.png differ diff --git a/artwork/ZeroTierIcon-WithBorder.png b/artwork/ZeroTierIcon-WithBorder.png new file mode 100644 index 0000000..b7f06d7 Binary files /dev/null and b/artwork/ZeroTierIcon-WithBorder.png differ diff --git a/artwork/ZeroTierIcon.icns b/artwork/ZeroTierIcon.icns new file mode 100644 index 0000000..17e60d5 Binary files /dev/null and b/artwork/ZeroTierIcon.icns differ diff --git a/artwork/ZeroTierIcon.ico b/artwork/ZeroTierIcon.ico new file mode 100644 index 0000000..2d190c4 Binary files /dev/null and b/artwork/ZeroTierIcon.ico differ diff --git a/artwork/ZeroTierIcon.png b/artwork/ZeroTierIcon.png new file mode 100644 index 0000000..4d9641b Binary files /dev/null and b/artwork/ZeroTierIcon.png differ diff --git a/artwork/ZeroTierIcon512x512.png b/artwork/ZeroTierIcon512x512.png new file mode 100644 index 0000000..d225c2e Binary files /dev/null and b/artwork/ZeroTierIcon512x512.png differ diff --git a/artwork/logo.html b/artwork/logo.html new file mode 100644 index 0000000..69c06a2 --- /dev/null +++ b/artwork/logo.html @@ -0,0 +1,37 @@ + + + + + +





+ +
+
+
+ + \ No newline at end of file diff --git a/attic/OSXEthernetTap.cpp.pcap-with-bridge-test b/attic/OSXEthernetTap.cpp.pcap-with-bridge-test new file mode 100644 index 0000000..baae0a4 --- /dev/null +++ b/attic/OSXEthernetTap.cpp.pcap-with-bridge-test @@ -0,0 +1,650 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// OSX compile fix... in6_var defines this in a struct which namespaces it for C++ ... why?!? +struct prf_ra { + u_char onlink : 1; + u_char autonomous : 1; + u_char reserved : 6; +} prf_ra; + +#include +#include + +// These are KERNEL_PRIVATE... why? +#ifndef SIOCAUTOCONF_START +#define SIOCAUTOCONF_START _IOWR('i', 132, struct in6_ifreq) /* accept rtadvd on this interface */ +#endif +#ifndef SIOCAUTOCONF_STOP +#define SIOCAUTOCONF_STOP _IOWR('i', 133, struct in6_ifreq) /* stop accepting rtadv for this interface */ +#endif + +#ifndef ETH_ALEN +#define ETH_ALEN 6 +#endif + +// -------------------------------------------------------------------------- +// -------------------------------------------------------------------------- +// This source is from: +// http://www.opensource.apple.com/source/Libinfo/Libinfo-406.17/gen.subproj/getifmaddrs.c?txt +// It's here because OSX 10.6 does not have this convenience function. + +#define SALIGN (sizeof(uint32_t) - 1) +#define SA_RLEN(sa) ((sa)->sa_len ? (((sa)->sa_len + SALIGN) & ~SALIGN) : \ +(SALIGN + 1)) +#define MAX_SYSCTL_TRY 5 +#define RTA_MASKS (RTA_GATEWAY | RTA_IFP | RTA_IFA) + +/* FreeBSD uses NET_RT_IFMALIST and RTM_NEWMADDR from */ +/* We can use NET_RT_IFLIST2 and RTM_NEWMADDR2 on Darwin */ +//#define DARWIN_COMPAT + +//#ifdef DARWIN_COMPAT +#define GIM_SYSCTL_MIB NET_RT_IFLIST2 +#define GIM_RTM_ADDR RTM_NEWMADDR2 +//#else +//#define GIM_SYSCTL_MIB NET_RT_IFMALIST +//#define GIM_RTM_ADDR RTM_NEWMADDR +//#endif + +// Not in 10.6 includes so use our own +struct _intl_ifmaddrs { + struct _intl_ifmaddrs *ifma_next; + struct sockaddr *ifma_name; + struct sockaddr *ifma_addr; + struct sockaddr *ifma_lladdr; +}; + +static inline int _intl_getifmaddrs(struct _intl_ifmaddrs **pif) +{ + int icnt = 1; + int dcnt = 0; + int ntry = 0; + size_t len; + size_t needed; + int mib[6]; + int i; + char *buf; + char *data; + char *next; + char *p; + struct ifma_msghdr2 *ifmam; + struct _intl_ifmaddrs *ifa, *ift; + struct rt_msghdr *rtm; + struct sockaddr *sa; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; /* protocol */ + mib[3] = 0; /* wildcard address family */ + mib[4] = GIM_SYSCTL_MIB; + mib[5] = 0; /* no flags */ + do { + if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) + return (-1); + if ((buf = (char *)malloc(needed)) == NULL) + return (-1); + if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) { + if (errno != ENOMEM || ++ntry >= MAX_SYSCTL_TRY) { + free(buf); + return (-1); + } + free(buf); + buf = NULL; + } + } while (buf == NULL); + + for (next = buf; next < buf + needed; next += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)(void *)next; + if (rtm->rtm_version != RTM_VERSION) + continue; + switch (rtm->rtm_type) { + case GIM_RTM_ADDR: + ifmam = (struct ifma_msghdr2 *)(void *)rtm; + if ((ifmam->ifmam_addrs & RTA_IFA) == 0) + break; + icnt++; + p = (char *)(ifmam + 1); + for (i = 0; i < RTAX_MAX; i++) { + if ((RTA_MASKS & ifmam->ifmam_addrs & + (1 << i)) == 0) + continue; + sa = (struct sockaddr *)(void *)p; + len = SA_RLEN(sa); + dcnt += len; + p += len; + } + break; + } + } + + data = (char *)malloc(sizeof(struct _intl_ifmaddrs) * icnt + dcnt); + if (data == NULL) { + free(buf); + return (-1); + } + + ifa = (struct _intl_ifmaddrs *)(void *)data; + data += sizeof(struct _intl_ifmaddrs) * icnt; + + memset(ifa, 0, sizeof(struct _intl_ifmaddrs) * icnt); + ift = ifa; + + for (next = buf; next < buf + needed; next += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)(void *)next; + if (rtm->rtm_version != RTM_VERSION) + continue; + + switch (rtm->rtm_type) { + case GIM_RTM_ADDR: + ifmam = (struct ifma_msghdr2 *)(void *)rtm; + if ((ifmam->ifmam_addrs & RTA_IFA) == 0) + break; + + p = (char *)(ifmam + 1); + for (i = 0; i < RTAX_MAX; i++) { + if ((RTA_MASKS & ifmam->ifmam_addrs & + (1 << i)) == 0) + continue; + sa = (struct sockaddr *)(void *)p; + len = SA_RLEN(sa); + switch (i) { + case RTAX_GATEWAY: + ift->ifma_lladdr = + (struct sockaddr *)(void *)data; + memcpy(data, p, len); + data += len; + break; + + case RTAX_IFP: + ift->ifma_name = + (struct sockaddr *)(void *)data; + memcpy(data, p, len); + data += len; + break; + + case RTAX_IFA: + ift->ifma_addr = + (struct sockaddr *)(void *)data; + memcpy(data, p, len); + data += len; + break; + + default: + data += len; + break; + } + p += len; + } + ift->ifma_next = ift + 1; + ift = ift->ifma_next; + break; + } + } + + free(buf); + + if (ift > ifa) { + ift--; + ift->ifma_next = NULL; + *pif = ifa; + } else { + *pif = NULL; + free(ifa); + } + return (0); +} + +static inline void _intl_freeifmaddrs(struct _intl_ifmaddrs *ifmp) +{ + free(ifmp); +} + +// -------------------------------------------------------------------------- +// -------------------------------------------------------------------------- + +#include +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" +#include "../node/Mutex.hpp" +#include "../node/Dictionary.hpp" +#include "OSUtils.hpp" +#include "OSXEthernetTap.hpp" + +// ff:ff:ff:ff:ff:ff with no ADI +static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); + +static inline bool _setIpv6Stuff(const char *ifname,bool performNUD,bool acceptRouterAdverts) +{ + struct in6_ndireq nd; + struct in6_ifreq ifr; + + int s = socket(AF_INET6,SOCK_DGRAM,0); + if (s <= 0) + return false; + + memset(&nd,0,sizeof(nd)); + strncpy(nd.ifname,ifname,sizeof(nd.ifname)); + + if (ioctl(s,SIOCGIFINFO_IN6,&nd)) { + close(s); + return false; + } + + unsigned long oldFlags = (unsigned long)nd.ndi.flags; + + if (performNUD) + nd.ndi.flags |= ND6_IFF_PERFORMNUD; + else nd.ndi.flags &= ~ND6_IFF_PERFORMNUD; + + if (oldFlags != (unsigned long)nd.ndi.flags) { + if (ioctl(s,SIOCSIFINFO_FLAGS,&nd)) { + close(s); + return false; + } + } + + memset(&ifr,0,sizeof(ifr)); + strncpy(ifr.ifr_name,ifname,sizeof(ifr.ifr_name)); + if (ioctl(s,acceptRouterAdverts ? SIOCAUTOCONF_START : SIOCAUTOCONF_STOP,&ifr)) { + close(s); + return false; + } + + close(s); + return true; +} + +namespace ZeroTier { + +static std::set globalDeviceNames; +static Mutex globalTapCreateLock; + +OSXEthernetTap::OSXEthernetTap( + const char *homePath, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *friendlyName, + void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *data,unsigned int len), + void *arg) : + _handler(handler), + _arg(arg), + _pcap((void *)0), + _nwid(nwid), + _mac(mac), + _homePath(homePath), + _mtu(mtu), + _metric(metric), + _enabled(true) +{ + char errbuf[PCAP_ERRBUF_SIZE]; + char devname[64],ethaddr[64],mtustr[32],metstr[32],nwids[32]; + + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid); + + if (mtu > 2800) + throw std::runtime_error("max tap MTU is 2800"); + + Mutex::Lock _gl(globalTapCreateLock); + + std::string desiredDevice; + Dictionary devmap; + { + std::string devmapbuf; + if (OSUtils::readFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmapbuf)) { + devmap.fromString(devmapbuf); + desiredDevice = devmap.get(nwids,""); + } + } + + if ((desiredDevice.length() >= 9)&&(desiredDevice.substr(0,6) == "bridge")) { + // length() >= 9 matches bridge### or bridge#### + _dev = desiredDevice; + } else { + if (globalDeviceNames.size() >= (10000 - 128)) // sanity check... this would be nuts + throw std::runtime_error("too many devices!"); + unsigned int pseudoBridgeNo = (unsigned int)((nwid ^ (nwid >> 32)) % (10000 - 128)) + 128; // range: bridge128 to bridge9999 + sprintf(devname,"bridge%u",pseudoBridgeNo); + while (globalDeviceNames.count(std::string(devname)) > 0) { + ++pseudoBridgeNo; + if (pseudoBridgeNo > 9999) + pseudoBridgeNo = 64; + sprintf(devname,"bridge%u",pseudoBridgeNo); + } + _dev = devname; + } + + // Configure MAC address and MTU, bring interface up + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"create",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + if (exitcode != 0) + throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface"); + } else throw std::runtime_error("unable to fork()"); + Utils::snprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]); + Utils::snprintf(mtustr,sizeof(mtustr),"%u",_mtu); + Utils::snprintf(metstr,sizeof(metstr),"%u",_metric); + cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + if (exitcode != 0) + throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface"); + } else throw std::runtime_error("unable to fork()"); + + _setIpv6Stuff(_dev.c_str(),true,false); + + _pcap = (void *)pcap_create(_dev.c_str(),errbuf); + if (!_pcap) { + cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"destroy",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + } + throw std::runtime_error((std::string("pcap_create() on new bridge device failed: ") + errbuf).c_str()); + } + pcap_set_promisc(reinterpret_cast(_pcap),1); + pcap_set_timeout(reinterpret_cast(_pcap),120000); + pcap_set_immediate_mode(reinterpret_cast(_pcap),1); + if (pcap_set_buffer_size(reinterpret_cast(_pcap),1024 * 1024 * 16) != 0) // 16MB + fprintf(stderr,"WARNING: pcap_set_buffer_size() failed!\n"); + if (pcap_set_snaplen(reinterpret_cast(_pcap),4096) != 0) + fprintf(stderr,"WARNING: pcap_set_snaplen() failed!\n"); + if (pcap_activate(reinterpret_cast(_pcap)) != 0) { + pcap_close(reinterpret_cast(_pcap)); + cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"destroy",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + } + throw std::runtime_error("pcap_activate() on new bridge device failed."); + } + + globalDeviceNames.insert(_dev); + + devmap[nwids] = _dev; + OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmap.toString()); + + _thread = Thread::start(this); +} + +OSXEthernetTap::~OSXEthernetTap() +{ + _enabled = false; + + Mutex::Lock _gl(globalTapCreateLock); + globalDeviceNames.erase(_dev); + + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"destroy",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + if (exitcode == 0) { + // Destroying the interface nukes pcap and terminates the thread. + Thread::join(_thread); + } + } + + pcap_close(reinterpret_cast(_pcap)); +} + +static bool ___removeIp(const std::string &_dev,const InetAddress &ip) +{ + long cpid = (long)vfork(); + if (cpid == 0) { + execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"inet",ip.toIpString().c_str(),"-alias",(const char *)0); + _exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + return false; // never reached, make compiler shut up about return value +} + +bool OSXEthernetTap::addIp(const InetAddress &ip) +{ + if (!ip) + return false; + + std::vector allIps(ips()); + if (std::binary_search(allIps.begin(),allIps.end(),ip)) + return true; + + // Remove and reconfigure if address is the same but netmask is different + for(std::vector::iterator i(allIps.begin());i!=allIps.end();++i) { + if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) { + if (___removeIp(_dev,*i)) + break; + } + } + + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } // else return false... + + return false; +} + +bool OSXEthernetTap::removeIp(const InetAddress &ip) +{ + if (!ip) + return true; + std::vector allIps(ips()); + if (!std::binary_search(allIps.begin(),allIps.end(),ip)) { + if (___removeIp(_dev,ip)) + return true; + } + return false; +} + +std::vector OSXEthernetTap::ips() const +{ + struct ifaddrs *ifa = (struct ifaddrs *)0; + if (getifaddrs(&ifa)) + return std::vector(); + + std::vector r; + + struct ifaddrs *p = ifa; + while (p) { + if ((!strcmp(p->ifa_name,_dev.c_str()))&&(p->ifa_addr)&&(p->ifa_netmask)&&(p->ifa_addr->sa_family == p->ifa_netmask->sa_family)) { + switch(p->ifa_addr->sa_family) { + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *)p->ifa_addr; + struct sockaddr_in *nm = (struct sockaddr_in *)p->ifa_netmask; + r.push_back(InetAddress(&(sin->sin_addr.s_addr),4,Utils::countBits((uint32_t)nm->sin_addr.s_addr))); + } break; + case AF_INET6: { + struct sockaddr_in6 *sin = (struct sockaddr_in6 *)p->ifa_addr; + struct sockaddr_in6 *nm = (struct sockaddr_in6 *)p->ifa_netmask; + uint32_t b[4]; + memcpy(b,nm->sin6_addr.s6_addr,sizeof(b)); + r.push_back(InetAddress(sin->sin6_addr.s6_addr,16,Utils::countBits(b[0]) + Utils::countBits(b[1]) + Utils::countBits(b[2]) + Utils::countBits(b[3]))); + } break; + } + } + p = p->ifa_next; + } + + if (ifa) + freeifaddrs(ifa); + + std::sort(r.begin(),r.end()); + std::unique(r.begin(),r.end()); + + return r; +} + +void OSXEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + char putBuf[4096]; + if ((len <= _mtu)&&(_enabled)) { + to.copyTo(putBuf,6); + from.copyTo(putBuf + 6,6); + *((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType); + memcpy(putBuf + 14,data,len); + len += 14; + int r = pcap_inject(reinterpret_cast(_pcap),putBuf,len); + if (r <= 0) { + printf("%s: pcap_inject() failed\n",_dev.c_str()); + return; + } + printf("%s: inject %s -> %s etherType==%u len=%u r==%d\n",_dev.c_str(),from.toString().c_str(),to.toString().c_str(),etherType,len,r); + } +} + +std::string OSXEthernetTap::deviceName() const +{ + return _dev; +} + +void OSXEthernetTap::setFriendlyName(const char *friendlyName) +{ +} + +void OSXEthernetTap::scanMulticastGroups(std::vector &added,std::vector &removed) +{ + std::vector newGroups; + + struct _intl_ifmaddrs *ifmap = (struct _intl_ifmaddrs *)0; + if (!_intl_getifmaddrs(&ifmap)) { + struct _intl_ifmaddrs *p = ifmap; + while (p) { + if (p->ifma_addr->sa_family == AF_LINK) { + struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name; + struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr; + if ((la->sdl_alen == 6)&&(in->sdl_nlen <= _dev.length())&&(!memcmp(_dev.data(),in->sdl_data,in->sdl_nlen))) + newGroups.push_back(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen,6),0)); + } + p = p->ifma_next; + } + _intl_freeifmaddrs(ifmap); + } + + std::vector allIps(ips()); + for(std::vector::iterator ip(allIps.begin());ip!=allIps.end();++ip) + newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip)); + + std::sort(newGroups.begin(),newGroups.end()); + std::unique(newGroups.begin(),newGroups.end()); + + for(std::vector::iterator m(newGroups.begin());m!=newGroups.end();++m) { + if (!std::binary_search(_multicastGroups.begin(),_multicastGroups.end(),*m)) + added.push_back(*m); + } + for(std::vector::iterator m(_multicastGroups.begin());m!=_multicastGroups.end();++m) { + if (!std::binary_search(newGroups.begin(),newGroups.end(),*m)) + removed.push_back(*m); + } + + _multicastGroups.swap(newGroups); +} + +static void _pcapHandler(u_char *ptr,const struct pcap_pkthdr *hdr,const u_char *data) +{ + OSXEthernetTap *tap = reinterpret_cast(ptr); + if (hdr->caplen > 14) { + MAC to(data,6); + MAC from(data + 6,6); + if (from == tap->_mac) { + unsigned int etherType = ntohs(((const uint16_t *)data)[6]); + printf("%s: %s -> %s etherType==%u len==%u\n",tap->_dev.c_str(),from.toString().c_str(),to.toString().c_str(),etherType,(unsigned int)hdr->caplen); + // TODO: VLAN support + tap->_handler(tap->_arg,tap->_nwid,from,to,etherType,0,(const void *)(data + 14),hdr->len - 14); + } + } +} + +void OSXEthernetTap::threadMain() + throw() +{ + pcap_loop(reinterpret_cast(_pcap),-1,&_pcapHandler,reinterpret_cast(this)); +} + +} // namespace ZeroTier diff --git a/attic/OSXEthernetTap.cpp.utun-work-in-progress b/attic/OSXEthernetTap.cpp.utun-work-in-progress new file mode 100644 index 0000000..f40483e --- /dev/null +++ b/attic/OSXEthernetTap.cpp.utun-work-in-progress @@ -0,0 +1,831 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// OSX compile fix... in6_var defines this in a struct which namespaces it for C++ ... why?!? +struct prf_ra { + u_char onlink : 1; + u_char autonomous : 1; + u_char reserved : 6; +} prf_ra; + +#include +#include + +// These are KERNEL_PRIVATE... why? +#ifndef SIOCAUTOCONF_START +#define SIOCAUTOCONF_START _IOWR('i', 132, struct in6_ifreq) /* accept rtadvd on this interface */ +#endif +#ifndef SIOCAUTOCONF_STOP +#define SIOCAUTOCONF_STOP _IOWR('i', 133, struct in6_ifreq) /* stop accepting rtadv for this interface */ +#endif + +// -------------------------------------------------------------------------- +// -------------------------------------------------------------------------- +// This source is from: +// http://www.opensource.apple.com/source/Libinfo/Libinfo-406.17/gen.subproj/getifmaddrs.c?txt +// It's here because OSX 10.6 does not have this convenience function. + +#define SALIGN (sizeof(uint32_t) - 1) +#define SA_RLEN(sa) ((sa)->sa_len ? (((sa)->sa_len + SALIGN) & ~SALIGN) : \ +(SALIGN + 1)) +#define MAX_SYSCTL_TRY 5 +#define RTA_MASKS (RTA_GATEWAY | RTA_IFP | RTA_IFA) + +/* FreeBSD uses NET_RT_IFMALIST and RTM_NEWMADDR from */ +/* We can use NET_RT_IFLIST2 and RTM_NEWMADDR2 on Darwin */ +//#define DARWIN_COMPAT + +//#ifdef DARWIN_COMPAT +#define GIM_SYSCTL_MIB NET_RT_IFLIST2 +#define GIM_RTM_ADDR RTM_NEWMADDR2 +//#else +//#define GIM_SYSCTL_MIB NET_RT_IFMALIST +//#define GIM_RTM_ADDR RTM_NEWMADDR +//#endif + +// Not in 10.6 includes so use our own +struct _intl_ifmaddrs { + struct _intl_ifmaddrs *ifma_next; + struct sockaddr *ifma_name; + struct sockaddr *ifma_addr; + struct sockaddr *ifma_lladdr; +}; + +static inline int _intl_getifmaddrs(struct _intl_ifmaddrs **pif) +{ + int icnt = 1; + int dcnt = 0; + int ntry = 0; + size_t len; + size_t needed; + int mib[6]; + int i; + char *buf; + char *data; + char *next; + char *p; + struct ifma_msghdr2 *ifmam; + struct _intl_ifmaddrs *ifa, *ift; + struct rt_msghdr *rtm; + struct sockaddr *sa; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; /* protocol */ + mib[3] = 0; /* wildcard address family */ + mib[4] = GIM_SYSCTL_MIB; + mib[5] = 0; /* no flags */ + do { + if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) + return (-1); + if ((buf = (char *)malloc(needed)) == NULL) + return (-1); + if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) { + if (errno != ENOMEM || ++ntry >= MAX_SYSCTL_TRY) { + free(buf); + return (-1); + } + free(buf); + buf = NULL; + } + } while (buf == NULL); + + for (next = buf; next < buf + needed; next += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)(void *)next; + if (rtm->rtm_version != RTM_VERSION) + continue; + switch (rtm->rtm_type) { + case GIM_RTM_ADDR: + ifmam = (struct ifma_msghdr2 *)(void *)rtm; + if ((ifmam->ifmam_addrs & RTA_IFA) == 0) + break; + icnt++; + p = (char *)(ifmam + 1); + for (i = 0; i < RTAX_MAX; i++) { + if ((RTA_MASKS & ifmam->ifmam_addrs & + (1 << i)) == 0) + continue; + sa = (struct sockaddr *)(void *)p; + len = SA_RLEN(sa); + dcnt += len; + p += len; + } + break; + } + } + + data = (char *)malloc(sizeof(struct _intl_ifmaddrs) * icnt + dcnt); + if (data == NULL) { + free(buf); + return (-1); + } + + ifa = (struct _intl_ifmaddrs *)(void *)data; + data += sizeof(struct _intl_ifmaddrs) * icnt; + + memset(ifa, 0, sizeof(struct _intl_ifmaddrs) * icnt); + ift = ifa; + + for (next = buf; next < buf + needed; next += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)(void *)next; + if (rtm->rtm_version != RTM_VERSION) + continue; + + switch (rtm->rtm_type) { + case GIM_RTM_ADDR: + ifmam = (struct ifma_msghdr2 *)(void *)rtm; + if ((ifmam->ifmam_addrs & RTA_IFA) == 0) + break; + + p = (char *)(ifmam + 1); + for (i = 0; i < RTAX_MAX; i++) { + if ((RTA_MASKS & ifmam->ifmam_addrs & + (1 << i)) == 0) + continue; + sa = (struct sockaddr *)(void *)p; + len = SA_RLEN(sa); + switch (i) { + case RTAX_GATEWAY: + ift->ifma_lladdr = + (struct sockaddr *)(void *)data; + memcpy(data, p, len); + data += len; + break; + + case RTAX_IFP: + ift->ifma_name = + (struct sockaddr *)(void *)data; + memcpy(data, p, len); + data += len; + break; + + case RTAX_IFA: + ift->ifma_addr = + (struct sockaddr *)(void *)data; + memcpy(data, p, len); + data += len; + break; + + default: + data += len; + break; + } + p += len; + } + ift->ifma_next = ift + 1; + ift = ift->ifma_next; + break; + } + } + + free(buf); + + if (ift > ifa) { + ift--; + ift->ifma_next = NULL; + *pif = ifa; + } else { + *pif = NULL; + free(ifa); + } + return (0); +} + +static inline void _intl_freeifmaddrs(struct _intl_ifmaddrs *ifmp) +{ + free(ifmp); +} + +// -------------------------------------------------------------------------- +// -------------------------------------------------------------------------- + +#include +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" +#include "../node/Mutex.hpp" +#include "../node/Dictionary.hpp" +#include "Arp.hpp" +#include "OSUtils.hpp" +#include "OSXEthernetTap.hpp" + +// ff:ff:ff:ff:ff:ff with no ADI +static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); + +static inline bool _setIpv6Stuff(const char *ifname,bool performNUD,bool acceptRouterAdverts) +{ + struct in6_ndireq nd; + struct in6_ifreq ifr; + + int s = socket(AF_INET6,SOCK_DGRAM,0); + if (s <= 0) + return false; + + memset(&nd,0,sizeof(nd)); + strncpy(nd.ifname,ifname,sizeof(nd.ifname)); + + if (ioctl(s,SIOCGIFINFO_IN6,&nd)) { + close(s); + return false; + } + + unsigned long oldFlags = (unsigned long)nd.ndi.flags; + + if (performNUD) + nd.ndi.flags |= ND6_IFF_PERFORMNUD; + else nd.ndi.flags &= ~ND6_IFF_PERFORMNUD; + + if (oldFlags != (unsigned long)nd.ndi.flags) { + if (ioctl(s,SIOCSIFINFO_FLAGS,&nd)) { + close(s); + return false; + } + } + + memset(&ifr,0,sizeof(ifr)); + strncpy(ifr.ifr_name,ifname,sizeof(ifr.ifr_name)); + if (ioctl(s,acceptRouterAdverts ? SIOCAUTOCONF_START : SIOCAUTOCONF_STOP,&ifr)) { + close(s); + return false; + } + + close(s); + return true; +} + +// Create an OSX-native utun device (utun# where # is desiredNumber) +// Adapted from public domain utun example code by Jonathan Levin +static int _make_utun(int desiredNumber) +{ + struct sockaddr_ctl sc; + struct ctl_info ctlInfo; + struct ifreq ifr; + + memset(&ctlInfo, 0, sizeof(ctlInfo)); + if (strlcpy(ctlInfo.ctl_name, UTUN_CONTROL_NAME, sizeof(ctlInfo.ctl_name)) >= sizeof(ctlInfo.ctl_name)) { + return -1; + } + + int fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL); + if (fd == -1) + return -1; + if (ioctl(fd, CTLIOCGINFO, &ctlInfo) == -1) { + close(fd); + return -1; + } + + sc.sc_id = ctlInfo.ctl_id; + sc.sc_len = sizeof(sc); + sc.sc_family = AF_SYSTEM; + sc.ss_sysaddr = AF_SYS_CONTROL; + sc.sc_unit = desiredNumber + 1; + + if (connect(fd, (struct sockaddr *)&sc, sizeof(sc)) == -1) { + close(fd); + return -1; + } + + memset(&ifr,0,sizeof(ifr)); + sprintf(ifr.ifr_name,"utun%d",desiredNumber); + if (ioctl(fd,SIOCGIFFLAGS,(void *)&ifr) < 0) { + printf("SIOCGIFFLAGS failed\n"); + } + ifr.ifr_flags &= ~IFF_POINTOPOINT; + if (ioctl(fd,SIOCSIFFLAGS,(void *)&ifr) < 0) { + printf("clear IFF_POINTOPOINT failed\n"); + } + + return fd; +} + +namespace ZeroTier { + +static long globalTapsRunning = 0; +static Mutex globalTapCreateLock; + +OSXEthernetTap::OSXEthernetTap( + const char *homePath, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *friendlyName, + void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *data,unsigned int len), + void *arg) : + _handler(handler), + _arg(arg), + _arp((Arp *)0), + _nwid(nwid), + _homePath(homePath), + _mtu(mtu), + _metric(metric), + _fd(0), + _utun(false), + _enabled(true) +{ + char devpath[64],ethaddr[64],mtustr[32],metstr[32],nwids[32]; + struct stat stattmp; + + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid); + + if (mtu > 2800) + throw std::runtime_error("max tap MTU is 2800"); + + Mutex::Lock _gl(globalTapCreateLock); + + // Read remembered previous device name, if any -- we'll try to reuse + Dictionary devmap; + std::string desiredDevice; + { + std::string devmapbuf; + if (OSUtils::readFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmapbuf)) { + devmap.fromString(devmapbuf); + desiredDevice = devmap.get(nwids,""); + } + } + + if (::stat((_homePath + ZT_PATH_SEPARATOR_S + "tap.kext").c_str(),&stattmp) == 0) { + // Try to init kext if it's there, otherwise revert to utun mode + + if (::stat("/dev/zt0",&stattmp)) { + long kextpid = (long)vfork(); + if (kextpid == 0) { + ::chdir(homePath); + OSUtils::redirectUnixOutputs("/dev/null",(const char *)0); + ::execl("/sbin/kextload","/sbin/kextload","-q","-repository",homePath,"tap.kext",(const char *)0); + ::_exit(-1); + } else if (kextpid > 0) { + int exitcode = -1; + ::waitpid(kextpid,&exitcode,0); + } + ::usleep(500); // give tap device driver time to start up and try again + if (::stat("/dev/zt0",&stattmp)) + _utun = true; + } + + if (!_utun) { + // See if we can re-use the last device we had. + bool recalledDevice = false; + if (desiredDevice.length() > 2) { + Utils::snprintf(devpath,sizeof(devpath),"/dev/%s",desiredDevice.c_str()); + if (stat(devpath,&stattmp) == 0) { + _fd = ::open(devpath,O_RDWR); + if (_fd > 0) { + _dev = desiredDevice; + recalledDevice = true; + } + } + } + + // Open the first unused tap device if we didn't recall a previous one. + if (!recalledDevice) { + for(int i=0;i<64;++i) { + Utils::snprintf(devpath,sizeof(devpath),"/dev/zt%d",i); + if (stat(devpath,&stattmp)) { + _utun = true; + break; + } + _fd = ::open(devpath,O_RDWR); + if (_fd > 0) { + char foo[16]; + Utils::snprintf(foo,sizeof(foo),"zt%d",i); + _dev = foo; + break; + } + } + } + if (_fd <= 0) + _utun = true; + } + } else { + _utun = true; + } + + if (_utun) { + // Use OSX built-in utun device if kext is not available or doesn't work + + int utunNo = 0; + + if ((desiredDevice.length() > 4)&&(desiredDevice.substr(0,4) == "utun")) { + utunNo = Utils::strToInt(desiredDevice.substr(4).c_str()); + if (utunNo >= 0) + _fd = _make_utun(utunNo); + } + + if (_fd <= 0) { + // Start at utun8 to leave lower utuns unused since other stuff might + // want them -- OpenVPN, cjdns, etc. I'm not sure if those are smart + // enough to scan upward like this. + for(utunNo=8;utunNo<=256;++utunNo) { + if ((_fd = _make_utun(utunNo)) > 0) + break; + } + } + + if (_fd <= 0) + throw std::runtime_error("unable to find/load ZeroTier tap driver OR use built-in utun driver in OSX; permission or system problem or too many open devices?"); + + Utils::snprintf(devpath,sizeof(devpath),"utun%d",utunNo); + _dev = devpath; + + // Configure address and bring it up + Utils::snprintf(mtustr,sizeof(mtustr),"%u",_mtu); + Utils::snprintf(metstr,sizeof(metstr),"%u",_metric); + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"mtu",mtustr,"metric",metstr,"up",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + if (exitcode) { + ::close(_fd); + throw std::runtime_error("ifconfig failure activating utun interface"); + } + } + + } else { + // Use our ZeroTier OSX tun/tap driver for zt# Ethernet tap device + + if (fcntl(_fd,F_SETFL,fcntl(_fd,F_GETFL) & ~O_NONBLOCK) == -1) { + ::close(_fd); + throw std::runtime_error("unable to set flags on file descriptor for TAP device"); + } + + // Configure MAC address and MTU, bring interface up + Utils::snprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]); + Utils::snprintf(mtustr,sizeof(mtustr),"%u",_mtu); + Utils::snprintf(metstr,sizeof(metstr),"%u",_metric); + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + if (exitcode) { + ::close(_fd); + throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface"); + } + } + + _setIpv6Stuff(_dev.c_str(),true,false); + } + + // Set close-on-exec so that devices cannot persist if we fork/exec for update + fcntl(_fd,F_SETFD,fcntl(_fd,F_GETFD) | FD_CLOEXEC); + + ::pipe(_shutdownSignalPipe); + + ++globalTapsRunning; + + devmap[nwids] = _dev; + OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),devmap.toString()); + + _thread = Thread::start(this); +} + +OSXEthernetTap::~OSXEthernetTap() +{ + Mutex::Lock _gl(globalTapCreateLock); + + ::write(_shutdownSignalPipe[1],(const void *)this,1); // writing a byte causes thread to exit + Thread::join(_thread); + + ::close(_fd); + ::close(_shutdownSignalPipe[0]); + ::close(_shutdownSignalPipe[1]); + + if (_utun) { + delete _arp; + } else { + if (--globalTapsRunning <= 0) { + globalTapsRunning = 0; // sanity check -- should not be possible + + char tmp[16384]; + sprintf(tmp,"%s/%s",_homePath.c_str(),"tap.kext"); + long kextpid = (long)vfork(); + if (kextpid == 0) { + OSUtils::redirectUnixOutputs("/dev/null",(const char *)0); + ::execl("/sbin/kextunload","/sbin/kextunload",tmp,(const char *)0); + ::_exit(-1); + } else if (kextpid > 0) { + int exitcode = -1; + ::waitpid(kextpid,&exitcode,0); + } + } + } +} + +void OSXEthernetTap::setEnabled(bool en) +{ + _enabled = en; + // TODO: interface status change +} + +bool OSXEthernetTap::enabled() const +{ + return _enabled; +} + +static bool ___removeIp(const std::string &_dev,const InetAddress &ip) +{ + long cpid = (long)vfork(); + if (cpid == 0) { + execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"inet",ip.toIpString().c_str(),"-alias",(const char *)0); + _exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + return false; // never reached, make compiler shut up about return value +} + +bool OSXEthernetTap::addIp(const InetAddress &ip) +{ + if (!ip) + return false; + + std::vector allIps(ips()); + if (std::binary_search(allIps.begin(),allIps.end(),ip)) + return true; + + // Remove and reconfigure if address is the same but netmask is different + for(std::vector::iterator i(allIps.begin());i!=allIps.end();++i) { + if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) { + if (___removeIp(_dev,*i)) + break; + } + } + + if (_utun) { + long cpid = (long)vfork(); + if (cpid == 0) { + if (ip.ss_family == AF_INET6) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"inet6",ip.toString().c_str(),"alias",(const char *)0); + } else { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.toString().c_str(),ip.toIpString().c_str(),"alias",(const char *)0); + } + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + + if (exitcode == 0) { + if (ip.ss_family == AF_INET) { + // Add route to network over tun for IPv4 -- otherwise it behaves + // as a simple point to point tunnel instead of a true route. + cpid = (long)vfork(); + if (cpid == 0) { + ::close(STDERR_FILENO); + ::close(STDOUT_FILENO); + ::execl("/sbin/route","/sbin/route","add",ip.network().toString().c_str(),ip.toIpString().c_str(),(const char *)0); + ::exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + } else return true; + } + } + } else { + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + } + + return false; +} + +bool OSXEthernetTap::removeIp(const InetAddress &ip) +{ + if (!ip) + return true; + std::vector allIps(ips()); + if (!std::binary_search(allIps.begin(),allIps.end(),ip)) { + if (___removeIp(_dev,ip)) + return true; + } + return false; +} + +std::vector OSXEthernetTap::ips() const +{ + struct ifaddrs *ifa = (struct ifaddrs *)0; + if (getifaddrs(&ifa)) + return std::vector(); + + std::vector r; + + struct ifaddrs *p = ifa; + while (p) { + if ((!strcmp(p->ifa_name,_dev.c_str()))&&(p->ifa_addr)&&(p->ifa_netmask)&&(p->ifa_addr->sa_family == p->ifa_netmask->sa_family)) { + switch(p->ifa_addr->sa_family) { + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *)p->ifa_addr; + struct sockaddr_in *nm = (struct sockaddr_in *)p->ifa_netmask; + r.push_back(InetAddress(&(sin->sin_addr.s_addr),4,Utils::countBits((uint32_t)nm->sin_addr.s_addr))); + } break; + case AF_INET6: { + struct sockaddr_in6 *sin = (struct sockaddr_in6 *)p->ifa_addr; + struct sockaddr_in6 *nm = (struct sockaddr_in6 *)p->ifa_netmask; + uint32_t b[4]; + memcpy(b,nm->sin6_addr.s6_addr,sizeof(b)); + r.push_back(InetAddress(sin->sin6_addr.s6_addr,16,Utils::countBits(b[0]) + Utils::countBits(b[1]) + Utils::countBits(b[2]) + Utils::countBits(b[3]))); + } break; + } + } + p = p->ifa_next; + } + + if (ifa) + freeifaddrs(ifa); + + std::sort(r.begin(),r.end()); + std::unique(r.begin(),r.end()); + + return r; +} + +void OSXEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + char putBuf[4096]; + if ((_fd > 0)&&(len <= _mtu)&&(_enabled)) { + to.copyTo(putBuf,6); + from.copyTo(putBuf + 6,6); + *((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType); + memcpy(putBuf + 14,data,len); + len += 14; + ::write(_fd,putBuf,len); + } +} + +std::string OSXEthernetTap::deviceName() const +{ + return _dev; +} + +void OSXEthernetTap::setFriendlyName(const char *friendlyName) +{ +} + +void OSXEthernetTap::scanMulticastGroups(std::vector &added,std::vector &removed) +{ + std::vector newGroups; + + struct _intl_ifmaddrs *ifmap = (struct _intl_ifmaddrs *)0; + if (!_intl_getifmaddrs(&ifmap)) { + struct _intl_ifmaddrs *p = ifmap; + while (p) { + if (p->ifma_addr->sa_family == AF_LINK) { + struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name; + struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr; + if ((la->sdl_alen == 6)&&(in->sdl_nlen <= _dev.length())&&(!memcmp(_dev.data(),in->sdl_data,in->sdl_nlen))) + newGroups.push_back(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen,6),0)); + } + p = p->ifma_next; + } + _intl_freeifmaddrs(ifmap); + } + + std::vector allIps(ips()); + for(std::vector::iterator ip(allIps.begin());ip!=allIps.end();++ip) + newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip)); + + std::sort(newGroups.begin(),newGroups.end()); + std::unique(newGroups.begin(),newGroups.end()); + + for(std::vector::iterator m(newGroups.begin());m!=newGroups.end();++m) { + if (!std::binary_search(_multicastGroups.begin(),_multicastGroups.end(),*m)) + added.push_back(*m); + } + for(std::vector::iterator m(_multicastGroups.begin());m!=_multicastGroups.end();++m) { + if (!std::binary_search(newGroups.begin(),newGroups.end(),*m)) + removed.push_back(*m); + } + + _multicastGroups.swap(newGroups); +} + +void OSXEthernetTap::threadMain() + throw() +{ + fd_set readfds,nullfds; + MAC to,from; + int n,nfds,r; + char getBuf[8194]; + + Thread::sleep(500); + + FD_ZERO(&readfds); + FD_ZERO(&nullfds); + nfds = (int)std::max(_shutdownSignalPipe[0],_fd) + 1; + + r = 0; + for(;;) { + FD_SET(_shutdownSignalPipe[0],&readfds); + FD_SET(_fd,&readfds); + select(nfds,&readfds,&nullfds,&nullfds,(struct timeval *)0); + + if (FD_ISSET(_shutdownSignalPipe[0],&readfds)) // writes to shutdown pipe terminate thread + break; + + if (FD_ISSET(_fd,&readfds)) { + n = (int)::read(_fd,getBuf + r,sizeof(getBuf) - r); + if (n < 0) { + if ((errno != EINTR)&&(errno != ETIMEDOUT)) + break; + } else { + // Some tap drivers like to send the ethernet frame and the + // payload in two chunks, so handle that by accumulating + // data until we have at least a frame. + r += n; + if (r > 14) { + if (r > ((int)_mtu + 14)) // sanity check for weird TAP behavior on some platforms + r = _mtu + 14; + + if (_enabled) { + to.setTo(getBuf,6); + from.setTo(getBuf + 6,6); + unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); + // TODO: VLAN support + _handler(_arg,_nwid,from,to,etherType,0,(const void *)(getBuf + 14),r - 14); + } + + r = 0; + } + } + } + } +} + +} // namespace ZeroTier diff --git a/attic/OSXEthernetTap.hpp.pcap-with-bridge-test b/attic/OSXEthernetTap.hpp.pcap-with-bridge-test new file mode 100644 index 0000000..33f1948 --- /dev/null +++ b/attic/OSXEthernetTap.hpp.pcap-with-bridge-test @@ -0,0 +1,96 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_OSXETHERNETTAP_HPP +#define ZT_OSXETHERNETTAP_HPP + +#include +#include + +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/MAC.hpp" +#include "../node/InetAddress.hpp" +#include "../node/MulticastGroup.hpp" + +#include "Thread.hpp" + +namespace ZeroTier { + +/** + * OSX Ethernet tap using ZeroTier kernel extension zt# devices + */ +class OSXEthernetTap +{ +public: + OSXEthernetTap( + const char *homePath, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *friendlyName, + void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void *arg); + + ~OSXEthernetTap(); + + inline void setEnabled(bool en) { _enabled = en; } + inline bool enabled() const { return _enabled; } + bool addIp(const InetAddress &ip); + bool removeIp(const InetAddress &ip); + std::vector ips() const; + void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + std::string deviceName() const; + void setFriendlyName(const char *friendlyName); + void scanMulticastGroups(std::vector &added,std::vector &removed); + + void threadMain() + throw(); + + // Private members of OSXEthernetTap have public visibility to be accessable + // from an internal bounce function; don't modify directly. + void (*_handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); + void *_arg; + void *_pcap; // pcap_t * + uint64_t _nwid; + MAC _mac; + Thread _thread; + std::string _homePath; + std::string _dev; + std::vector _multicastGroups; + unsigned int _mtu; + unsigned int _metric; + volatile bool _enabled; +}; + +} // namespace ZeroTier + +#endif diff --git a/attic/OSXEthernetTap.hpp.utun-work-in-progress b/attic/OSXEthernetTap.hpp.utun-work-in-progress new file mode 100644 index 0000000..8ece87b --- /dev/null +++ b/attic/OSXEthernetTap.hpp.utun-work-in-progress @@ -0,0 +1,101 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_OSXETHERNETTAP_HPP +#define ZT_OSXETHERNETTAP_HPP + +#include +#include + +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/MAC.hpp" +#include "../node/InetAddress.hpp" +#include "../node/MulticastGroup.hpp" + +#include "Thread.hpp" + +namespace ZeroTier { + +class Arp; + +/** + * OSX Ethernet tap supporting either ZeroTier tun/tap kext or OSX-native utun + */ +class OSXEthernetTap +{ +public: + OSXEthernetTap( + const char *homePath, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *friendlyName, + void (*handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void *arg); + + ~OSXEthernetTap(); + + void setEnabled(bool en); + bool enabled() const; + bool addIp(const InetAddress &ip); + bool removeIp(const InetAddress &ip); + std::vector ips() const; + void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + std::string deviceName() const; + void setFriendlyName(const char *friendlyName); + void scanMulticastGroups(std::vector &added,std::vector &removed); + + inline bool isNativeUtun() const { return _utun; } + + void threadMain() + throw(); + +private: + void (*_handler)(void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); + void *_arg; + Arp *_arp; // created and used if utun is enabled + uint64_t _nwid; + Thread _thread; + std::string _homePath; + std::string _dev; + std::vector _multicastGroups; + unsigned int _mtu; + unsigned int _metric; + int _fd; + int _shutdownSignalPipe[2]; + bool _utun; + volatile bool _enabled; +}; + +} // namespace ZeroTier + +#endif diff --git a/attic/README.md b/attic/README.md new file mode 100644 index 0000000..768bccd --- /dev/null +++ b/attic/README.md @@ -0,0 +1,4 @@ +Retired Code and Miscellaneous Junk +====== + +This directory is for old code that isn't used but we don't want to lose track of, and for anything else random like debug scripts. diff --git a/attic/big-http-test/2015-11-10_01_50000.out.xz b/attic/big-http-test/2015-11-10_01_50000.out.xz new file mode 100644 index 0000000..d3e2a66 Binary files /dev/null and b/attic/big-http-test/2015-11-10_01_50000.out.xz differ diff --git a/attic/big-http-test/2015-11-10_02_50000.out.xz b/attic/big-http-test/2015-11-10_02_50000.out.xz new file mode 100644 index 0000000..0154da7 Binary files /dev/null and b/attic/big-http-test/2015-11-10_02_50000.out.xz differ diff --git a/attic/big-http-test/2015-11-10_03_12500_ec2-east-only.out.xz b/attic/big-http-test/2015-11-10_03_12500_ec2-east-only.out.xz new file mode 100644 index 0000000..3ae3555 Binary files /dev/null and b/attic/big-http-test/2015-11-10_03_12500_ec2-east-only.out.xz differ diff --git a/attic/big-http-test/Dockerfile b/attic/big-http-test/Dockerfile new file mode 100644 index 0000000..e19b3fe --- /dev/null +++ b/attic/big-http-test/Dockerfile @@ -0,0 +1,24 @@ +FROM centos:latest + +MAINTAINER https://www.zerotier.com/ + +EXPOSE 9993/udp + +ADD nodesource-el.repo /etc/yum.repos.d/nodesource-el.repo +RUN yum -y update && yum install -y nodejs && yum clean all + +RUN mkdir -p /var/lib/zerotier-one +RUN mkdir -p /var/lib/zerotier-one/networks.d +RUN touch /var/lib/zerotier-one/networks.d/ffffffffffffffff.conf + +ADD package.json / +RUN npm install + +ADD zerotier-one / +RUN chmod a+x /zerotier-one + +ADD agent.js / +ADD docker-main.sh / +RUN chmod a+x /docker-main.sh + +CMD ["./docker-main.sh"] diff --git a/attic/big-http-test/README.md b/attic/big-http-test/README.md new file mode 100644 index 0000000..23a9560 --- /dev/null +++ b/attic/big-http-test/README.md @@ -0,0 +1,12 @@ +HTTP one-to-all test +====== + +*This is really internal use code. You're free to test it out but expect to do some editing/tweaking to make it work. We used this to run some massive scale tests of our new geo-cluster-based root server infrastructure prior to taking it live.* + +Before using this code you will want to edit agent.js to change SERVER_HOST to the IP address of where you will run server.js. This should typically be an open Internet IP, since this makes reporting not dependent upon the thing being tested. Also note that this thing does no security of any kind. It's designed for one-off tests run over a short period of time, not to be anything that runs permanently. You will also want to edit the Dockerfile if you want to build containers and change the network ID to the network you want to run tests over. + +This code can be deployed across a large number of VMs or containers to test and benchmark HTTP traffic within a virtual network at scale. The agent acts as a server and can query other agents, while the server collects agent data and tells agents about each other. It's designed to use RFC4193-based ZeroTier IPv6 addresses within the cluster, which allows the easy provisioning of a large cluster without IP conflicts. + +The Dockerfile builds an image that launches the agent. The image must be "docker run" with "--device=/dev/net/tun --privileged" to permit it to open a tun/tap device within the container. (Unfortunately CAP_NET_ADMIN may not work due to a bug in Docker and/or Linux.) You can run a bunch with a command like: + + for ((n=0;n<10;n++)); do docker run --device=/dev/net/tun --privileged -d zerotier/http-test; done diff --git a/attic/big-http-test/agent.js b/attic/big-http-test/agent.js new file mode 100644 index 0000000..9ab2e01 --- /dev/null +++ b/attic/big-http-test/agent.js @@ -0,0 +1,196 @@ +// ZeroTier distributed HTTP test agent + +// --------------------------------------------------------------------------- +// Customizable parameters: + +// Time between startup and first test attempt +var TEST_STARTUP_LAG = 10000; + +// Maximum interval between test attempts (actual timing is random % this) +var TEST_INTERVAL_MAX = (60000 * 10); + +// Test timeout in ms +var TEST_TIMEOUT = 30000; + +// Where should I get other agents' IDs and POST results? +var SERVER_HOST = '52.26.196.147'; +var SERVER_PORT = 18080; + +// Which port do agents use to serve up test data to each other? +var AGENT_PORT = 18888; + +// Payload size in bytes +var PAYLOAD_SIZE = 5000; + +// --------------------------------------------------------------------------- + +var ipaddr = require('ipaddr.js'); +var os = require('os'); +var http = require('http'); +var async = require('async'); + +var express = require('express'); +var app = express(); + +// Find our ZeroTier-assigned RFC4193 IPv6 address +var thisAgentId = null; +var interfaces = os.networkInterfaces(); +if (!interfaces) { + console.error('FATAL: os.networkInterfaces() failed.'); + process.exit(1); +} +for(var ifname in interfaces) { + var ifaddrs = interfaces[ifname]; + if (Array.isArray(ifaddrs)) { + for(var i=0;i 1) { + + var target = agents[Math.floor(Math.random() * agents.length)]; + while (target === thisAgentId) + target = agents[Math.floor(Math.random() * agents.length)]; + + var testRequest = null; + var timeoutId = null; + timeoutId = setTimeout(function() { + if (testRequest !== null) + testRequest.abort(); + timeoutId = null; + },TEST_TIMEOUT); + var startTime = Date.now(); + + testRequest = http.get({ + host: agentIdToIp(target), + port: AGENT_PORT, + path: '/' + },function(res) { + var bytes = 0; + res.on('data',function(chunk) { bytes += chunk.length; }); + res.on('end',function() { + lastTestResult = { + source: thisAgentId, + target: target, + time: (Date.now() - startTime), + bytes: bytes, + timedOut: (timeoutId === null), + error: null + }; + if (timeoutId !== null) + clearTimeout(timeoutId); + return setTimeout(doTest,Math.round(Math.random() * TEST_INTERVAL_MAX) + 1); + }); + }).on('error',function(e) { + lastTestResult = { + source: thisAgentId, + target: target, + time: (Date.now() - startTime), + bytes: 0, + timedOut: (timeoutId === null), + error: e.toString() + }; + if (timeoutId !== null) + clearTimeout(timeoutId); + return setTimeout(doTest,Math.round(Math.random() * TEST_INTERVAL_MAX) + 1); + }); + + } else { + return setTimeout(doTest,1000); + } + + }); + }).on('error',function(e) { + console.log('POST failed: '+e.toString()); + return setTimeout(doTest,1000); + }); + if (lastTestResult !== null) { + submit.write(JSON.stringify(lastTestResult)); + lastTestResult = null; + } + submit.end(); +}; + +// Agents just serve up a test payload +app.get('/',function(req,res) { return res.status(200).send(payload); }); + +var expressServer = app.listen(AGENT_PORT,function () { + // Start timeout-based loop + setTimeout(doTest(),TEST_STARTUP_LAG); +}); diff --git a/attic/big-http-test/big-test-kill.sh b/attic/big-http-test/big-test-kill.sh new file mode 100755 index 0000000..fa7f3cc --- /dev/null +++ b/attic/big-http-test/big-test-kill.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Kills all running Docker containers on all big-test-hosts + +export PATH=/bin:/usr/bin:/usr/local/bin:/usr/sbin:/sbin + +pssh -h big-test-hosts -x '-t -t' -i -OUserKnownHostsFile=/dev/null -OStrictHostKeyChecking=no -t 0 -p 256 "sudo docker ps -aq | xargs -r sudo docker rm -f" + +exit 0 diff --git a/attic/big-http-test/big-test-start.sh b/attic/big-http-test/big-test-start.sh new file mode 100755 index 0000000..2411eed --- /dev/null +++ b/attic/big-http-test/big-test-start.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# More than 500 container seems to result in a lot of sporadic failures, probably due to Linux kernel scaling issues with virtual network ports +# 250 with a 16GB RAM VM like Amazon m4.xlarge seems good +NUM_CONTAINERS=250 +CONTAINER_IMAGE=zerotier/http-test +SCALE_UP_DELAY=10 + +export PATH=/bin:/usr/bin:/usr/local/bin:/usr/sbin:/sbin + +pssh -h big-test-hosts -x '-t -t' -i -OUserKnownHostsFile=/dev/null -OStrictHostKeyChecking=no -t 0 -p 256 "sudo sysctl -w net.netfilter.nf_conntrack_max=262144 ; for ((n=0;n<$NUM_CONTAINERS;n++)); do sudo docker run --device=/dev/net/tun --privileged -d $CONTAINER_IMAGE; sleep $SCALE_UP_DELAY; done" + +exit 0 diff --git a/attic/big-http-test/crunch-results.js b/attic/big-http-test/crunch-results.js new file mode 100644 index 0000000..50e5c49 --- /dev/null +++ b/attic/big-http-test/crunch-results.js @@ -0,0 +1,65 @@ +// +// Pipe the output of server.js into this to convert raw test results into bracketed statistics +// suitable for graphing. +// + +// Time duration per statistical bracket +var BRACKET_SIZE = 10000; + +// Number of bytes expected from each test +var EXPECTED_BYTES = 5000; + +var readline = require('readline'); +var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false +}); + +var count = 0.0; +var overallCount = 0.0; +var totalFailures = 0.0; +var totalOverallFailures = 0.0; +var totalMs = 0; +var totalData = 0; +var devices = {}; +var lastBracketTs = 0; + +rl.on('line',function(line) { + line = line.trim(); + var ls = line.split(','); + if (ls.length == 7) { + var ts = parseInt(ls[0]); + var fromId = ls[1]; + var toId = ls[2]; + var ms = parseFloat(ls[3]); + var bytes = parseInt(ls[4]); + var timedOut = (ls[5] == 'true') ? true : false; + var errMsg = ls[6]; + + count += 1.0; + overallCount += 1.0; + if ((bytes !== EXPECTED_BYTES)||(timedOut)) { + totalFailures += 1.0; + totalOverallFailures += 1.0; + } + totalMs += ms; + totalData += bytes; + + devices[fromId] = true; + devices[toId] = true; + + if (lastBracketTs === 0) + lastBracketTs = ts; + + if (((ts - lastBracketTs) >= BRACKET_SIZE)&&(count > 0.0)) { + console.log(count.toString()+','+overallCount.toString()+','+(totalMs / count)+','+(totalFailures / count)+','+(totalOverallFailures / overallCount)+','+totalData+','+Object.keys(devices).length); + + count = 0.0; + totalFailures = 0.0; + totalMs = 0; + totalData = 0; + lastBracketTs = ts; + } + } // else ignore junk +}); diff --git a/attic/big-http-test/docker-main.sh b/attic/big-http-test/docker-main.sh new file mode 100755 index 0000000..29cdced --- /dev/null +++ b/attic/big-http-test/docker-main.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +export PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin + +/zerotier-one -d >>zerotier-one.out 2>&1 + +# Wait for ZeroTier to start and join the network +while [ ! -d "/proc/sys/net/ipv6/conf/zt0" ]; do + sleep 0.25 +done + +# Wait just a bit longer for stuff to settle +sleep 5 + +exec node --harmony /agent.js >>agent.out 2>&1 +#exec node --harmony /agent.js diff --git a/attic/big-http-test/nodesource-el.repo b/attic/big-http-test/nodesource-el.repo new file mode 100644 index 0000000..b785d3d --- /dev/null +++ b/attic/big-http-test/nodesource-el.repo @@ -0,0 +1,6 @@ +[nodesource] +name=Node.js Packages for Enterprise Linux 7 - $basearch +baseurl=https://rpm.nodesource.com/pub_4.x/el/7/$basearch +failovermethod=priority +enabled=1 +gpgcheck=0 diff --git a/attic/big-http-test/package.json b/attic/big-http-test/package.json new file mode 100644 index 0000000..173a6f9 --- /dev/null +++ b/attic/big-http-test/package.json @@ -0,0 +1,16 @@ +{ + "name": "zerotier-test-http", + "version": "1.0.0", + "description": "ZeroTier in-network HTTP test", + "main": "agent.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "ZeroTier, Inc.", + "license": "GPL-3.0", + "dependencies": { + "async": "^1.5.0", + "express": "^4.13.3", + "ipaddr.js": "^1.0.3" + } +} diff --git a/attic/big-http-test/server.js b/attic/big-http-test/server.js new file mode 100644 index 0000000..629784d --- /dev/null +++ b/attic/big-http-test/server.js @@ -0,0 +1,53 @@ +// ZeroTier distributed HTTP test coordinator and result-reporting server + +// --------------------------------------------------------------------------- +// Customizable parameters: + +var SERVER_PORT = 18080; + +// --------------------------------------------------------------------------- + +var fs = require('fs'); + +var express = require('express'); +var app = express(); + +app.use(function(req,res,next) { + req.rawBody = ''; + req.on('data', function(chunk) { req.rawBody += chunk.toString(); }); + req.on('end', function() { return next(); }); +}); + +var knownAgents = {}; + +app.post('/:agentId',function(req,res) { + var agentId = req.params.agentId; + if ((!agentId)||(agentId.length !== 32)) + return res.status(404).send(''); + + if (req.rawBody) { + var receiveTime = Date.now(); + var resultData = null; + try { + resultData = JSON.parse(req.rawBody); + console.log(Date.now().toString()+','+resultData.source+','+resultData.target+','+resultData.time+','+resultData.bytes+','+resultData.timedOut+',"'+((resultData.error) ? resultData.error : '')+'"'); + } catch (e) {} + } + + knownAgents[agentId] = true; + var thisUpdate = []; + var agents = Object.keys(knownAgents); + if (agents.length < 100) + thisUpdate = agents; + else { + for(var xx=0;xx<100;++xx) + thisUpdate.push(agents[Math.floor(Math.random() * agents.length)]); + } + + return res.status(200).send(JSON.stringify(thisUpdate)); +}); + +var expressServer = app.listen(SERVER_PORT,function () { + console.log('LISTENING ON '+SERVER_PORT); + console.log(''); +}); diff --git a/attic/cli/README.md b/attic/cli/README.md new file mode 100644 index 0000000..595df07 --- /dev/null +++ b/attic/cli/README.md @@ -0,0 +1,57 @@ +The new ZeroTier CLI! +==== + +With this update we've expanded upon the previous CLI's functionality, so things should seem pretty familiar. Here are some of the new features we've introduced: + + - Create and administer networks on ZeroTier Central directly from the console. + - Service configurations, allows you to control local/remote instances of ZeroTier One + - Identity generation and management is now part of the same CLI tool + +*** +## Configurations + +Configurations are a way for you to nickname and logically organize the control of ZeroTier services running locally or remotely (this includes ZeroTier Central!). They're merely groupings of service API url's and auth tokens. The CLI's settings data is contained within `.zerotierCliSettings`. + +For instance, you can control your local instance of ZeroTier One via the `@local` config. By default it is represented as follows: + +``` +"local": { + "auth": "7tyqRoFytajf21j2l2t9QPm5", + "type": "one", + "url": "http://127.0.0.1:9993/" +} +``` + +As an example, if you issue the command `zerotier ls` is it implicitly stating `zerotier @local ls`. + +With the same line of thinking, you could create a `@my.zerotier.com` which would allow for something like `zerotier @my.zerotier.com net-create` which talks to our hosted ZeroTier Central to create a new network. + + + +## Command families + +- `cli-` is for configuring the settings data for the CLI itself, such as adding/removing `@thing` configurations, variables, etc. +- `net-` is for operating on a *ZeroTier Central* service such as `https://my.zerotier.com` +- `id-` is for handling ZeroTier identities. + +And those commands with no prefix are there to allow you to operate ZeroTier One instances either local or remote. + +*** +## Useful command examples + +*Add a ZeroTier One configuration:* + + - `zerotier cli-add-zt MyLocalConfigName https://127.0.0.1:9993/ ` + +*Add a ZeroTier Central configuration:* + + - `zerotier cli-add-central MyZTCentralConfigName https://my.zerotier.com/ ` + +*Set a default ZeroTier One instance:* + + - `zerotier cli-set defaultOne MyLocalConfigName` + +*Set a default ZeroTier Central:* + + - `zerotier cli-set defaultCentral MyZTCentralConfigName` + diff --git a/attic/cli/zerotier.cpp b/attic/cli/zerotier.cpp new file mode 100644 index 0000000..e75268d --- /dev/null +++ b/attic/cli/zerotier.cpp @@ -0,0 +1,957 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +// Note: unlike the rest of ZT's code base, this requires C++11 due to +// the JSON library it uses and other things. + +#include +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Identity.hpp" +#include "../version.h" +#include "../osdep/OSUtils.hpp" +#include "../ext/offbase/json/json.hpp" + +#ifdef __WINDOWS__ +#include +#include +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +using json = nlohmann::json; +using namespace ZeroTier; + +#define ZT_CLI_FLAG_VERBOSE 'v' +#define ZT_CLI_FLAG_UNSAFE_SSL 'X' + +#define REQ_GET 0 +#define REQ_POST 1 +#define REQ_DEL 2 + +#define OK_STR "[OK ]: " +#define FAIL_STR "[FAIL]: " +#define WARN_STR "[WARN]: " +#define INVALID_ARGS_STR "Invalid args. Usage: " + +struct CLIState +{ + std::string atname; + std::string command; + std::string url; + std::map reqHeaders; + std::vector args; + std::map opts; + json settings; +}; + +namespace { + +static Identity getIdFromArg(char *arg) +{ + Identity id; + if ((strlen(arg) > 32)&&(arg[10] == ':')) { // identity is a literal on the command line + if (id.fromString(arg)) + return id; + } else { // identity is to be read from a file + std::string idser; + if (OSUtils::readFile(arg,idser)) { + if (id.fromString(idser)) + return id; + } + } + return Identity(); +} + +static std::string trimString(const std::string &s) +{ + unsigned long end = (unsigned long)s.length(); + while (end) { + char c = s[end - 1]; + if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t')) + --end; + else break; + } + unsigned long start = 0; + while (start < end) { + char c = s[start]; + if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t')) + ++start; + else break; + } + return s.substr(start,end - start); +} + +static inline std::string getSettingsFilePath() +{ +#ifdef __WINDOWS__ +#else + const char *home = getenv("HOME"); + if (!home) + home = "/"; + return (std::string(home) + "/.zerotierCliSettings"); +#endif +} + +static bool saveSettingsBackup(CLIState &state) +{ + std::string sfp(getSettingsFilePath().c_str()); + if(state.settings.find("generateBackupConfig") != state.settings.end() + && state.settings["generateBackupConfig"].get() == "true") { + std::string backup_file = getSettingsFilePath() + ".bak"; + if(!OSUtils::writeFile(sfp.c_str(), state.settings.dump(2))) { + OSUtils::lockDownFile(sfp.c_str(),false); + std::cout << WARN_STR << "unable to write backup config file" << std::endl; + return false; + } + return true; + } + return false; +} + +static bool saveSettings(CLIState &state) +{ + std::string sfp(getSettingsFilePath().c_str()); + if(OSUtils::writeFile(sfp.c_str(), state.settings.dump(2))) { + OSUtils::lockDownFile(sfp.c_str(),false); + std::cout << OK_STR << "changes saved." << std::endl; + return true; + } + std::cout << FAIL_STR << "unable to write to " << sfp << std::endl; + return false; +} + +static void dumpHelp() +{ + std::cout << "ZeroTier Newer-Spiffier CLI " << ZEROTIER_ONE_VERSION_MAJOR << "." << ZEROTIER_ONE_VERSION_MINOR << "." << ZEROTIER_ONE_VERSION_REVISION << std::endl; + std::cout << "(c)2016 ZeroTier, Inc. / Licensed under the GNU GPL v3" << std::endl; + std::cout << std::endl; + std::cout << "Configuration path: " << getSettingsFilePath() << std::endl; + std::cout << std::endl; + std::cout << "Usage: zerotier [-option] [@name] []" << std::endl; + std::cout << std::endl; + std::cout << "Options:" << std::endl; + std::cout << " -verbose - Verbose JSON output" << std::endl; + std::cout << " -X - Do not check SSL certs (CAUTION!)" << std::endl; + std::cout << std::endl; + std::cout << "CLI Configuration Commands:" << std::endl; + std::cout << " cli-set - Set a CLI option ('cli-set help')" << std::endl; + std::cout << " cli-unset - Un-sets a CLI option ('cli-unset help')" << std::endl; + std::cout << " cli-ls - List configured @things" << std::endl; + std::cout << " cli-rm @name - Remove a configured @thing" << std::endl; + std::cout << " cli-add-zt @name - Add a ZeroTier service" << std::endl; + std::cout << " cli-add-central @name - Add ZeroTier Central instance" << std::endl; + std::cout << std::endl; + std::cout << "ZeroTier One Service Commands:" << std::endl; + std::cout << " -v / -version - Displays default local instance's version'" << std::endl; + std::cout << " ls - List currently joined networks" << std::endl; + std::cout << " join [opt=value ...] - Join a network" << std::endl; + std::cout << " leave - Leave a network" << std::endl; + std::cout << " peers - List ZeroTier VL1 peers" << std::endl; + std::cout << " show [] - Get info about self or object" << std::endl; + std::cout << std::endl; + std::cout << "Network Controller Commands:" << std::endl; + std::cout << " net-create - Create a new network" << std::endl; + std::cout << " net-rm - Delete a network (CAUTION!)" << std::endl; + std::cout << " net-ls - List administered networks" << std::endl; + std::cout << " net-members - List members of a network" << std::endl; + std::cout << " net-show [
] - Get network or member info" << std::endl; + std::cout << " net-auth
- Authorize a member" << std::endl; + std::cout << " net-unauth
- De-authorize a member" << std::endl; + std::cout << " net-set - See 'net-set help'" << std::endl; + std::cout << std::endl; + std::cout << "Identity Commands:" << std::endl; + std::cout << " id-generate [] - Generate a ZeroTier identity" << std::endl; + std::cout << " id-validate - Locally validate an identity" << std::endl; + std::cout << " id-sign - Sign a file" << std::endl; + std::cout << " id-verify - Verify a file's signature" << std::endl; + std::cout << " id-getpublic - Get full identity's public portion" << std::endl; + std::cout << std::endl; +} + +static size_t _curlStringAppendCallback(void *contents,size_t size,size_t nmemb,void *stdstring) +{ + size_t totalSize = size * nmemb; + reinterpret_cast(stdstring)->append((const char *)contents,totalSize); + return totalSize; +} + +static std::tuple REQUEST(int requestType, CLIState &state, const std::map &headers, const std::string &postfield, const std::string &url) +{ + std::string body; + char errbuf[CURL_ERROR_SIZE]; + char urlbuf[4096]; + + CURL *curl; + curl = curl_easy_init(); + if (!curl) { + std::cerr << "FATAL: curl_easy_init() failed" << std::endl; + exit(-1); + } + + Utils::scopy(urlbuf,sizeof(urlbuf),url.c_str()); + curl_easy_setopt(curl,CURLOPT_URL,urlbuf); + + struct curl_slist *hdrs = (struct curl_slist *)0; + for(std::map::const_iterator i(headers.begin());i!=headers.end();++i) { + std::string htmp(i->first); + htmp.append(": "); + htmp.append(i->second); + hdrs = curl_slist_append(hdrs,htmp.c_str()); + } + if (hdrs) + curl_easy_setopt(curl,CURLOPT_HTTPHEADER,hdrs); + + //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); + curl_easy_setopt(curl,CURLOPT_WRITEDATA,(void *)&body); + curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,_curlStringAppendCallback); + + if(std::find(state.args.begin(), state.args.end(), "-X") == state.args.end()) + curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,(state.opts.count(ZT_CLI_FLAG_UNSAFE_SSL) > 0) ? 0L : 1L); + + if(requestType == REQ_POST) { + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfield.c_str()); + } + if(requestType == REQ_DEL) + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + if(requestType == REQ_GET) { + curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errbuf); + curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,0L); + } + + curl_easy_setopt(curl,CURLOPT_USERAGENT,"ZeroTier-CLI"); + CURLcode res = curl_easy_perform(curl); + + errbuf[CURL_ERROR_SIZE-1] = (char)0; // sanity check + + if (res != CURLE_OK) + return std::make_tuple(-1,std::string(errbuf)); + + long response_code; + int rc = (int)curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE, &response_code); + + if(response_code == 401) { std::cout << FAIL_STR << response_code << "Unauthorized." << std::endl; exit(0); } + else if(response_code == 403) { std::cout << FAIL_STR << response_code << "Forbidden." << std::endl; exit(0); } + else if(response_code == 404) { std::cout << FAIL_STR << response_code << "Not found." << std::endl; exit(0); } + else if(response_code == 408) { std::cout << FAIL_STR << response_code << "Request timed out." << std::endl; exit(0); } + else if(response_code != 200) { std::cout << FAIL_STR << response_code << "Unable to process request." << std::endl; exit(0); } + + curl_easy_cleanup(curl); + if (hdrs) + curl_slist_free_all(hdrs); + return std::make_tuple(response_code,body); +} + +} // anonymous namespace + +////////////////////////////////////////////////////////////////////////////// + +// Check for user-specified @thing config +// Make sure it @thing makes sense +// Apply appropriate request headers +static void checkForThing(CLIState &state, std::string thingType, bool warnNoThingProvided) +{ + std::string configName; + if(state.atname.length()) { + configName = state.atname.erase(0,1); + // make sure specified @thing makes sense in the context of the command + if(thingType == "one" && state.settings["things"][configName]["type"].get() != "one") { + std::cout << FAIL_STR << "A ZeroTier Central config was specified for a ZeroTier One command." << std::endl; + exit(0); + } + if(thingType == "central" && state.settings["things"][configName]["type"].get() != "central") { + std::cout << FAIL_STR << "A ZeroTier One config was specified for a ZeroTier Central command." << std::endl; + exit(0); + } + } + else { // no @thing specified, check for defaults depending on type + if(thingType == "one") { + if(state.settings.find("defaultOne") != state.settings.end()) { + if(warnNoThingProvided) + std::cout << WARN_STR << "No @thing specified, assuming default for ZeroTier One command: " << state.settings["defaultOne"].get().c_str() << std::endl; + configName = state.settings["defaultOne"].get().erase(0,1); // get default + } + else { + std::cout << WARN_STR << "No @thing specified, and no default is known." << std::endl; + std::cout << "HELP: To set a default: zerotier cli-set defaultOne @my_default_thing" << std::endl; + exit(0); + } + } + if(thingType == "central") { + if(state.settings.find("defaultCentral") != state.settings.end()) { + if(warnNoThingProvided) + std::cout << WARN_STR << "No @thing specified, assuming default for ZeroTier Central command: " << state.settings["defaultCentral"].get().c_str() << std::endl; + configName = state.settings["defaultCentral"].get().erase(0,1); // get default + } + else { + std::cout << WARN_STR << "No @thing specified, and no default is known." << std::endl; + std::cout << "HELP: To set a default: zerotier cli-set defaultCentral @my_default_thing" << std::endl; + exit(0); + } + } + } + // Apply headers + if(thingType == "one") { + state.reqHeaders["X-ZT1-Auth"] = state.settings["things"][configName]["auth"]; + } + if(thingType == "central"){ + state.reqHeaders["Content-Type"] = "application/json"; + state.reqHeaders["Authorization"] = "Bearer " + state.settings["things"][configName]["auth"].get(); + state.reqHeaders["Accept"] = "application/json"; + } + state.url = state.settings["things"][configName]["url"]; +} + +static bool checkURL(std::string url) +{ + // TODO + return true; +} + +static std::string getLocalVersion(CLIState &state) +{ + json result; + std::tuple res; + checkForThing(state,"one",false); + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "/status"); + if(std::get<0>(res) == 200) { + result = json::parse(std::get<1>(res)); + return result["version"].get(); + } + return "---"; +} + +#ifdef __WINDOWS__ +int _tmain(int argc, _TCHAR* argv[]) +#else +int main(int argc,char **argv) +#endif +{ +#ifdef __WINDOWS__ + { + WSADATA wsaData; + WSAStartup(MAKEWORD(2,2),&wsaData); + } +#endif + + curl_global_init(CURL_GLOBAL_DEFAULT); + CLIState state; + std::string arg1, arg2, authToken; + + for(int i=1;i 0)&&(port < 65536))&&(authToken.length() > 0)) { + state.settings["things"]["local"]["url"] = (std::string("http://127.0.0.1:") + portStr + "/"); + state.settings["things"]["local"]["auth"] = authToken; + initSuccess = true; + } + } + + if (!saveSettings(state)) { + std::cerr << "FATAL: unable to write " << getSettingsFilePath() << std::endl; + exit(-1); + } + + if (initSuccess) { + std::cerr << "INFO: initialized new config at " << getSettingsFilePath() << std::endl; + } else { + std::cerr << "INFO: initialized new config at " << getSettingsFilePath() << " but could not auto-init local ZeroTier One service config from " << oneHome << " -- you will need to set local service URL and port manually if you want to control a local instance of ZeroTier One. (This happens if you are not root/administrator.)" << std::endl; + } + } + } + + // PRE-REQUEST SETUP + + json result; + std::tuple res; + std::string url = ""; + + // META + + if ((state.command.length() == 0)||(state.command == "help")) { + dumpHelp(); + return -1; + } + + // zerotier version + else if (state.command == "v" || state.command == "version") { + std::cout << getLocalVersion(state) << std::endl; + return 1; + } + + // zerotier cli-set + else if (state.command == "cli-set") { + if(argc != 4) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-set " << std::endl; + return 1; + } + std::string settingName, settingValue; + if(state.atname.length()) { // User provided @thing erroneously, we will ignore it and adjust argument indices + settingName = argv[3]; + settingValue = argv[4]; + } + else { + settingName = argv[2]; + settingValue = argv[3]; + } + saveSettingsBackup(state); + state.settings[settingName] = settingValue; // changes + saveSettings(state); + } + + // zerotier cli-unset + else if (state.command == "cli-unset") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-unset " << std::endl; + return 1; + } + std::string settingName; + if(state.atname.length()) // User provided @thing erroneously, we will ignore it and adjust argument indices + settingName = argv[3]; + else + settingName = argv[2]; + saveSettingsBackup(state); + state.settings.erase(settingName); // changes + saveSettings(state); + } + + // zerotier @thing_to_remove cli-rm --- removes the configuration + else if (state.command == "cli-rm") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-rm <@thing>" << std::endl; + return 1; + } + if(state.settings["things"].find(state.atname) != state.settings["things"].end()) { + if(state.settings["defaultOne"] == state.atname) { + std::cout << "WARNING: The config you're trying to remove is currently set as your default. Set a new default first!" << std::endl; + std::cout << " | Usage: zerotier set defaultOne @your_other_thing" << std::endl; + } + else { + state.settings["things"].erase(state.atname.c_str()); + saveSettings(state); + } + } + } + + // zerotier cli-add-zt + // TODO: Check for malformed urls/auth + else if (state.command == "cli-add-zt") { + if(argc != 5) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-add-zt " << std::endl; + return 1; + } + std::string thing_name = argv[2], url = argv[3], auth = argv[4]; + if(!checkURL(url)) { + std::cout << FAIL_STR << "Malformed URL" << std::endl; + return 1; + } + if(state.settings.find(thing_name) != state.settings.end()) { + std::cout << "WARNING: A @thing with the shortname " << thing_name.c_str() + << " already exists. Choose another name or rename the old @thing" << std::endl; + std::cout << " | Usage: To rename a @thing: zerotier cli-rename @old_thing_name @new_thing_name" << std::endl; + } + else { + result = json::parse("{ \"auth\": \"" + auth + "\", \"type\": \"" + "one" + "\", \"url\": \"" + url + "\" }"); + saveSettingsBackup(state); + // TODO: Handle cases where user may or may not prepend an @ + state.settings["things"][thing_name] = result; // changes + saveSettings(state); + } + } + + // zerotier cli-add-central + // TODO: Check for malformed urls/auth + else if (state.command == "cli-add-central") { + if(argc != 5) { + std::cerr << INVALID_ARGS_STR << "zerotier cli-add-central " << std::endl; + return 1; + } + std::string thing_name = argv[2], url = argv[3], auth = argv[4]; + if(!checkURL(url)) { + std::cout << FAIL_STR << "Malformed URL" << std::endl; + return 1; + } + if(state.settings.find(thing_name) != state.settings.end()) { + std::cout << "WARNING: A @thing with the shortname " << thing_name.c_str() + << " already exists. Choose another name or rename the old @thing" << std::endl; + std::cout << " | Usage: To rename a @thing: zerotier cli-rename @old_thing_name @new_thing_name" << std::endl; + } + else { + result = json::parse("{ \"auth\": \"" + auth + "\", \"type\": \"" + "central" + "\", \"url\": \"" + url + "\" }"); + saveSettingsBackup(state); + // TODO: Handle cases where user may or may not prepend an @ + state.settings["things"]["@" + thing_name] = result; // changes + saveSettings(state); + } + } + + // ONE SERVICE + + // zerotier ls --- display all networks currently joined + else if (state.command == "ls" || state.command == "listnetworks") { + if(argc != 2) { + std::cerr << INVALID_ARGS_STR << "zerotier ls" << std::endl; + return 1; + } + checkForThing(state,"one",true); + url = state.url + "network"; + res = REQUEST(REQ_GET,state,state.reqHeaders,"",(const std::string)url); + if(std::get<0>(res) == 200) { + std::cout << "listnetworks " << std::endl; + auto j = json::parse(std::get<1>(res).c_str()); + if (j.type() == json::value_t::array) { + for(int i=0;i(); + std::string name = j[i]["name"].get(); + std::string mac = j[i]["mac"].get(); + std::string status = j[i]["status"].get(); + std::string type = j[i]["type"].get(); + std::string addrs; + for(int m=0; m() + " "; + } + std::string dev = j[i]["portDeviceName"].get(); + std::cout << "listnetworks " << nwid << " " << name << " " << mac << " " << status << " " << type << " " << dev << " " << addrs << std::endl; + } + } + } + } + + // zerotier join --- joins a network + else if (state.command == "join") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier join " << std::endl; + return 1; + } + checkForThing(state,"one",true); + res = REQUEST(REQ_POST,state,state.reqHeaders,"{}",state.url + "/network/" + state.args[0]); + if(std::get<0>(res) == 200) { + std::cout << OK_STR << "connected to " << state.args[0] << std::endl; + } + } + + // zerotier leave --- leaves a network + else if (state.command == "leave") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier leave " << std::endl; + return 1; + } + checkForThing(state,"one",true); + res = REQUEST(REQ_DEL,state,state.reqHeaders,"{}",state.url + "/network/" + state.args[0]); + if(std::get<0>(res) == 200) { + std::cout << OK_STR << "disconnected from " << state.args[0] << std::endl; + } + } + + // zerotier peers --- display address and role of all peers + else if (state.command == "peers") { + if(argc != 2) { + std::cerr << INVALID_ARGS_STR << "zerotier peers" << std::endl; + return 1; + } + checkForThing(state,"one",true); + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "/peer"); + if(std::get<0>(res) == 200) { + json result = json::parse(std::get<1>(res)); + for(int i=0; i(res) == 200) { + result = json::parse(std::get<1>(res)); + std::string status_str = result["online"].get() ? "ONLINE" : "OFFLINE"; + std::cout << "info " << result["address"].get() + << " " << status_str << " " << result["version"].get() << std::endl; + } + } + + // REMOTE + + // zerotier @thing net-create --- creates a new network + else if (state.command == "net-create") { + if(argc > 3 || (argc == 3 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-create" << std::endl; + return 1; + } + checkForThing(state,"central",true); + res = REQUEST(REQ_POST,state,state.reqHeaders,"",state.url + "api/network"); + if(std::get<0>(res) == 200) { + json result = json::parse(std::get<1>(res)); + std::cout << OK_STR << "created network " << result["config"]["nwid"].get() << std::endl; + } + } + + // zerotier @thing net-rm --- deletes a network + else if (state.command == "net-rm") { + if(argc > 4 || (argc == 4 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-rm " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(!state.args.size()) { + std::cout << "Argument error: No network specified." << std::endl; + std::cout << " | Usage: zerotier net-rm " << std::endl; + } + else { + std::string nwid = state.args[0]; + res = REQUEST(REQ_DEL,state,state.reqHeaders,"",state.url + "api/network/" + nwid); + if(std::get<0>(res) == 200) { + std::cout << "deleted network " << nwid << std::endl; + } + } + } + + // zerotier @thing net-ls --- lists all networks + else if (state.command == "net-ls") { + if(argc > 3 || (argc == 3 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-ls" << std::endl; + return 1; + } + checkForThing(state,"central",true); + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network"); + if(std::get<0>(res) == 200) { + json result = json::parse(std::get<1>(res)); + for(int m=0;m() << std::endl; + } + } + } + + // zerotier @thing net-members --- show all members of a network + else if (state.command == "net-members") { + if(argc > 4 || (argc == 4 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-members " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(!state.args.size()) { + std::cout << FAIL_STR << "Argument error: No network specified." << std::endl; + std::cout << " | Usage: zerotier net-members " << std::endl; + } + else { + std::string nwid = state.args[0]; + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member"); + json result = json::parse(std::get<1>(res)); + std::cout << "Members of " << nwid << ":" << std::endl; + for (json::iterator it = result.begin(); it != result.end(); ++it) { + std::cout << it.key() << std::endl; + } + } + } + + // zerotier @thing net-show --- show info about a device on a specific network + else if (state.command == "net-show") { + if(argc > 5 || (argc == 5 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-show " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(state.args.size() < 2) { + std::cout << FAIL_STR << "Argument error: Too few arguments." << std::endl; + std::cout << " | Usage: zerotier net-show " << std::endl; + } + else { + std::string nwid = state.args[0]; + std::string devid = state.args[1]; + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member/" + devid); + // TODO: More info, what would we like to show exactly? + if(std::get<0>(res) == 200) { + json result = json::parse(std::get<1>(res)); + std::cout << "Assigned IP: " << std::endl; + for(int m=0; m() << std::endl; + } + } + } + } + + // zerotier @thing net-auth --- authorize a device on a network + else if (state.command == "net-auth") { + if(argc > 5 || (argc == 5 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-auth " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(state.args.size() != 2) { + std::cout << FAIL_STR << "Argument error: Network and/or device ID not specified." << std::endl; + std::cout << " | Usage: zerotier net-auth " << std::endl; + } + std::string nwid = state.args[0]; + std::string devid = state.args[1]; + url = state.url + "api/network/" + nwid + "/member/" + devid; + // Add device to network + res = REQUEST(REQ_POST,state,state.reqHeaders,"",(const std::string)url); + if(std::get<0>(res) == 200) { + result = json::parse(std::get<1>(res)); + res = REQUEST(REQ_GET,state,state.reqHeaders,"",(const std::string)url); + result = json::parse(std::get<1>(res)); + result["config"]["authorized"] = "true"; + std::string newconfig = result.dump(); + res = REQUEST(REQ_POST,state,state.reqHeaders,newconfig,(const std::string)url); + if(std::get<0>(res) == 200) + std::cout << OK_STR << devid << " authorized on " << nwid << std::endl; + else + std::cout << FAIL_STR << "There was a problem authorizing that device." << std::endl; + } + } + + // zerotier @thing net-unauth + else if (state.command == "net-unauth") { + if(argc > 5 || (argc == 5 && !state.atname.length())) { + std::cerr << INVALID_ARGS_STR << "zerotier <@thing> net-unauth " << std::endl; + return 1; + } + checkForThing(state,"central",true); + if(state.args.size() != 2) { + std::cout << FAIL_STR << "Bad argument. No network and/or device ID specified." << std::endl; + std::cout << " | Usage: zerotier net-unauth " << std::endl; + } + std::string nwid = state.args[0]; + std::string devid = state.args[1]; + // If successful, get member config + res = REQUEST(REQ_GET,state,state.reqHeaders,"",state.url + "api/network/" + nwid + "/member/" + devid); + result = json::parse(std::get<1>(res)); + // modify auth field and re-POST + result["config"]["authorized"] = "false"; + std::string newconfig = result.dump(); + res = REQUEST(REQ_POST,state,state.reqHeaders,newconfig,state.url + "api/network/" + nwid + "/member/" + devid); + if(std::get<0>(res) == 200) + std::cout << OK_STR << devid << " de-authorized from " << nwid << std::endl; + else + std::cout << FAIL_STR << "There was a problem de-authorizing that device." << std::endl; + } + + // zerotier @thing net-set + else if (state.command == "net-set") { + } + + // ID + + // zerotier id-generate [] + else if (state.command == "id-generate") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier id-generate []" << std::endl; + return 1; + } + uint64_t vanity = 0; + int vanityBits = 0; + if (argc >= 5) { + vanity = Utils::hexStrToU64(argv[4]) & 0xffffffffffULL; + vanityBits = 4 * strlen(argv[4]); + if (vanityBits > 40) + vanityBits = 40; + } + + ZeroTier::Identity id; + for(;;) { + id.generate(); + if ((id.address().toInt() >> (40 - vanityBits)) == vanity) { + if (vanityBits > 0) { + fprintf(stderr,"vanity address: found %.10llx !\n",(unsigned long long)id.address().toInt()); + } + break; + } else { + fprintf(stderr,"vanity address: tried %.10llx looking for first %d bits of %.10llx\n",(unsigned long long)id.address().toInt(),vanityBits,(unsigned long long)(vanity << (40 - vanityBits))); + } + } + + std::string idser = id.toString(true); + if (argc >= 3) { + if (!OSUtils::writeFile(argv[2],idser)) { + std::cerr << "Error writing to " << argv[2] << std::endl; + return 1; + } else std::cout << argv[2] << " written" << std::endl; + if (argc >= 4) { + idser = id.toString(false); + if (!OSUtils::writeFile(argv[3],idser)) { + std::cerr << "Error writing to " << argv[3] << std::endl; + return 1; + } else std::cout << argv[3] << " written" << std::endl; + } + } else std::cout << idser << std::endl; + } + + // zerotier id-validate + else if (state.command == "id-validate") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier id-validate " << std::endl; + return 1; + } + Identity id = getIdFromArg(argv[2]); + if (!id) { + std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; + return 1; + } + if (!id.locallyValidate()) { + std::cerr << argv[2] << " FAILED validation." << std::endl; + return 1; + } else std::cout << argv[2] << "is a valid identity" << std::endl; + } + + // zerotier id-sign + else if (state.command == "id-sign") { + if(argc != 4) { + std::cerr << INVALID_ARGS_STR << "zerotier id-sign " << std::endl; + return 1; + } + Identity id = getIdFromArg(argv[2]); + if (!id) { + std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; + return 1; + } + if (!id.hasPrivate()) { + std::cerr << argv[2] << " does not contain a private key (must use private to sign)" << std::endl; + return 1; + } + std::string inf; + if (!OSUtils::readFile(argv[3],inf)) { + std::cerr << argv[3] << " is not readable" << std::endl; + return 1; + } + C25519::Signature signature = id.sign(inf.data(),(unsigned int)inf.length()); + std::cout << Utils::hex(signature.data,(unsigned int)signature.size()) << std::endl; + } + + // zerotier id-verify + else if (state.command == "id-verify") { + if(argc != 4) { + std::cerr << INVALID_ARGS_STR << "zerotier id-verify " << std::endl; + return 1; + } + Identity id = getIdFromArg(argv[2]); + if (!id) { + std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; + return 1; + } + std::string inf; + if (!OSUtils::readFile(argv[3],inf)) { + std::cerr << argv[3] << " is not readable" << std::endl; + return 1; + } + std::string signature(Utils::unhex(argv[4])); + if ((signature.length() > ZT_ADDRESS_LENGTH)&&(id.verify(inf.data(),(unsigned int)inf.length(),signature.data(),(unsigned int)signature.length()))) { + std::cout << argv[3] << " signature valid" << std::endl; + } else { + std::cerr << argv[3] << " signature check FAILED" << std::endl; + return 1; + } + } + + // zerotier id-getpublic + else if (state.command == "id-getpublic") { + if(argc != 3) { + std::cerr << INVALID_ARGS_STR << "zerotier id-getpublic " << std::endl; + return 1; + } + Identity id = getIdFromArg(argv[2]); + if (!id) { + std::cerr << "Identity argument invalid or file unreadable: " << argv[2] << std::endl; + return 1; + } + std::cerr << id.toString(false) << std::endl; + } + // + else { + dumpHelp(); + return -1; + } + if(std::find(state.args.begin(), state.args.end(), "-verbose") != state.args.end()) + std::cout << "\n\nAPI response = " << std::get<1>(res) << std::endl; + curl_global_cleanup(); + return 0; +} diff --git a/attic/historic/anode_protocol.txt b/attic/historic/anode_protocol.txt new file mode 100644 index 0000000..0adb923 --- /dev/null +++ b/attic/historic/anode_protocol.txt @@ -0,0 +1,764 @@ +***************************************************************************** +Anode Protocol Specification Draft +Version 0.8 + +(c)2009-2010 Adam Ierymenko +***************************************************************************** + +Table of Contents + +***************************************************************************** + +1. Introduction + +Anode provides three components that work together to provide a global, +secure, and mobile addressing system for computer networks: + +1) An addressing system based on public key cryptography enabling network + devices or applications to assign themselves secure, unique, and globally + reachable network addresses in a flat address space. + +2) A system enabling network participants holding global addresses to locate + one another on local or global networks with "zero configuration." + +3) A communications protocol for communication between addressed network + participants that requires no special operating system support and no + changes to existing network infrastructure. + +Using Anode, both fixed and mobile applications and devices can communicate +directly as if they were all connected to the same VPN. Anode restores the +original vision of the Internet as a "flat" network where anything can talk +to anything, and adds the added benefits of address mobility and strong +protection against address spoofing and other protocol level attacks. + +1.1. Design Philosophy + +Anode's design philosophy is the classical "KISS" principle: "Keep It Simple +Stupid." Anode's design principles are: + +#1: Do not try to solve too many problems at once, and stay in scope. + +Anode does not attempt to solve too many problems at once. It attempts to +solve the problems of mobile addressing, address portability, and "flat" +addressing in the presence of NAT or other barriers. + +It does not attempt to duplicate the full functionality of SSL, X.509, SSH, +XMPP, an enterprise service bus, a pub/sub architecture, BitTorrent, etc. All +of those protocols and services can be used over Anode if their functionality +is desired. + +#2: Avoid state management. + +State multiplies the complexity and failure modes of network protocols. State +also tends to get in the way of the achievement of new features implicitly +(see principle #4). Avoid state whenever possible. + +#3: Avoid algorithm and dependency bloat. + +Anode uses only elliptic curve Diffie-Hellman (EC-DH) and AES-256. No other +cryptographic algorithms or hash functions are presently necessary. This +yields implementations compact enough for embedded devices. + +Anode also requires few or no dependencies, depending on whether the two +needed cryptographic algorithms are obtained through a library or included. +No other protocols or libraries are required in an implementation. + +#4: Achieve features implicitly. + +Use a simple stateless design that allows features to be achieved implicitly +rather than specified explicitly. For example, Anode can do multi-homing and +could be used to build a mesh network, but neither of these features is +explicitly specified. + +***************************************************************************** + +2. Core Concepts and Algorithms + +This section describes addresses, zones, common algorithms, and other core +concepts. + +2.1. Zones + +A zone is a 32-bit integer encoded into every Anode address. Zones serve to +assist in the location of peers by address on global IP networks. They are +not presently significant for local communications, though they could be +used to partition addresses into groups or link them with configuration +options. + +Each zone has a corresponding zone file which can be fetched in a number of +ways (see below). A zone file is a flat text format dictionary of the format +"key=value" separated by carriage returns. Line feeds are ignored, and any +character may be escaped with a backslash (\) character. Blank lines are +ignored. + +The following entries must appear in a zone file: + +n= +d= +c= +r= +ttl= + +Additional fields may appear as well, including fields specific to special +applications or protocols supported within the zone. Some of these are +defined in this document. + +Zone file fetching mechanisms are described below. Multiple mechanisms are +specified to enable fallback in the event that one mechanism is not available. + +2.1.1. Zone File Retrieval + +Zone files are retrieved via HTTP, with the HTTP address being formed in one +of two ways. + +The preferred DNS method: + +To fetch a zone file via DNS, use the zone ID to generate a host name and URI +of the form: + + http://a--XXXXXXXX.net/z + +The XXXXXXXX field is the zone ID in hexadecimal. + +The fallback IP method: + +For fallback in the absence of DNS, the zone ID can be used directly as an +IPv4 or IPv4-mapped-to-IPv6 IP address. A URI is generated of the form: + + http://ip_address/z + +Support for this method requires that a zone ID be chosen to correspond to a +permanent IPv4 (preferably mappable to IPv6 space as well) IP address. + +2.1.2. Zone ID Reservation + +By convention, a zone ID is considered reserved when a domain of the form +"a--XXXXXXXX.net" (where XXXXXXXX is the ID in hex) is registered. + +It is recommended that this be done even for zone IDs not used for global +address location in order to globally reserve them. + +2.2. Addresses + +Anode addresses are binary strings containing a 32-bit zone ID, a public key, +and possibly other fields. Only one address type is presently defined: + +|---------------------------------------------------------------------------| +| Name | Type ID | Elliptic Curve Parameters | Total Length | +|---------------------------------------------------------------------------| +| ANODE-256-40 | 1 | NIST-P-256 | 40 | +|---------------------------------------------------------------------------| + +|---------------------------------------------------------------------------| +| Name | Binary Layout | +|---------------------------------------------------------------------------| +| ANODE-256-40 | | +|---------------------------------------------------------------------------| + +The public key is a "compressed" form elliptic curve public key as described +in RFC5480. + +The unused section of the address must be zero. These bytes are reserved for +future use. + +2.2.1. ASCII Format For Addresses + +Addresses are encoded in ASCII using base-32, which provides a quotable and +printable encoding that is of manageable length and is case-insensitive. For +example, an ANODE-256-40 address is 64 characters long in base-32 encoding. + +2.3. Relaying + +An Anode peer may optionally relay packets to any other reachable peer. +Relaying is accomplished by sending a packet to a peer with the recipient set +to the final recipient. The receiving peer will, if relaying is allowed and if +it knows of or can reach the recipient, forward the packet. + +No error is returned if relaying fails, so relay paths are treated as possible +paths for communication until a return is received in the same way as direct +paths. + +Relaying can be used by peers to send messages indirectly, locate one +another, and determine network location information to facilitate the +establishment of direct communications. + +Peers may refuse to relay or may limit the transmission rate at which packets +can be relayed. + +2.3.1. Zone Relays + +If a zone's addresses are globally reachable on global IP networks, it must +have one or more zone relays. These must have globally reachable public +static IP addresses. + +Zone relays are specified in the zone file in the following format: + + zr.
=[,]::: + +The address checksum is the sum of the bytes in the Anode address modulus +the number of "zr" entries, in hexadecimal. For example, if a zone had four +global relays its zone file could contain the lines: + + zr.0=1.2.3.4:4343:4344:klj4j3... + zr.1=2.3.4.5:4343:4344:00194j... + zr.2=3.4.5.6:4343:4344:1j42zz... + zr.3=4.5.6.7:4343:4344:z94j1q... + +The relay would be chosen by taking the sum of the bytes in the address +modulo 4. For example, if the bytes of an address sum to 5081 then relay +zr.1 would be used to communicate with that address. + +If more than one IP address is listed for a given relay, the peer must choose +at random from among the addresses of the desired type (IPv4 or IPv6). + +Each relay must have one Anode address for every address type supported within +the zone. (At present there is only one address type defined.) + +Peers should prefer UDP and fall back to TCP only if UDP is not available. + +To make itself available, a peer must make itself known to its designated zone +relay. This is accomplished by sending a PING message. + +2.4. Key Agreement and Derivation + +Key agreement is performed using elliptic curve Diffie-Hellman. This yields +a raw key whose size depends on the elliptic curve parameters in use. + +The following algorithm is used to derive a key of any length from a raw +key generated through key agreement: + +1) Zero the derived key buffer. +2) Determine the largest of the original raw key or the derived key. +3) Loop from 0 to the largest length determined in step 2, XOR each byte of + the derived key buffer with the corresponding byte of the original key + buffer with each index being modulus the length of the respective buffer. + +2.5. Message Authentication + +For message authentication, CMAC-AES (with AES-256) is used. This is also +known in some literature as OMAC1-AES. The key is derived from key agreement +between the key pair of the sending peer and the address of the recipient. + +2.6. AES-DIGEST + +To maintain cryptographic algorithm frugality, a cryptographic hash function +is constructed from the AES-256 cipher. This hash function uses the common +Davis-Meyer construction with Merkle-Damgård length padding. + +It is described by the following pseudocode: + + byte previous_digest[16] + byte digest[16] = { 0,0,... } + byte block[32] = { 0,0,... } + integer block_counter = 0 + + ; digest message + for each byte b of message + block[block_counter] = b + block_counter = block_counter + 1 + if block_counter == 32 then + block_counter = 0 + save digest[] in previous_digest[] + encrypt digest[] with aes-256 using block[] as 256-bit aes-256 key + xor digest[] with previous_digest[] + end if + next + + ; append end marker, do final block + block[block_counter] = 0x80 + block_counter = block_counter + 1 + zero rest of block[] from block_counter to 15 + save digest[] in previous_digest[] + encrypt digest[] with aes-256 using block[] as 256-bit aes-256 key + xor digest[] with previous_digest[] + + ; Merkle-Damgård length padding + zero first 8 bytes of block[] + fill last 8 bytes of block[] w/64-bit length in big-endian order + save digest[] in previous_digest[] + encrypt digest[] with aes-256 using block[] as 256-bit aes-128 key + xor digest[] with previous_digest[] + + ; digest[] now contains 128-bit message digest + +2.7. Short Address Identifiers (Address IDs) + +A short 8-byte version of the Anode address is used in the protocol to reduce +transmission overhead when both sides are already aware of the other's full +address. + +The short address identifier is formed by computing the AES-DIGEST of the +full address and then XORing the first 8 bytes of the digest with the last +8 bytes to yield an 8-byte shortened digest. + +2.8. DNS Resolution of Anode Addresses + +Anode addresses can be saved in DNS TXT records in the following format: + +anode:
+ +This permits Anode addresses to be resolved from normal DNS host name. + +2.9. Packet Transmission Mechanisms + +2.9.1. UDP Transmission + +The recommended method of sending Anode packets is UDP. Each packet is simply +sent as a UDP packet. + +2.9.2. TCP Transmission + +To send packets over TCP, each packet is prefixed by its size as a 16-bit +integer. + +2.9.3. HTTP Transmission + +Anode packets may be submitted in HTTP POST transactions for transport over +networks where HTTP is the only available protocol. + +Anode packets are simply prefixed with a 16-byte packet size and concatenated +together just as they are in a TCP stream. One or more packets may be sent +with each HTTP POST transaction for improved performance. + +Since this method is intended for use in "hostile" or highly restricted +circumstances, no additional details such as special headers or MIME types +are specified to allow maximum flexibility. Peers should ignore anything +other than the payload. + +2.10. Endpoints + +An endpoint indicates a place where Anode packets may be sent. The following +endpoint types are specified: + +|---------------------------------------------------------------------------| +| Endpoint Type | Description | Address Format | +|---------------------------------------------------------------------------| +| 0x00 | Unspecified | (none) | +| 0x01 | Ethernet | | +| 0x02 | UDP/IPv4 | | +| 0x03 | TCP/IPv4 | | +| 0x04 | UDP/IPv6 | | +| 0x05 | TCP/IPv6 | | +| 0x06 | HTTP | | +|---------------------------------------------------------------------------| + +Endpoints are encoded by beginning with a single byte indicating the endpoint +type followed by the address information required for the given type. + +Note that IP ports bear no relationship to Anode protocol ports. + +2.11. Notes + +All integers in the protocol are transmitted in network (big endian) byte +order. + +***************************************************************************** + +3. Common Packet Format + +A common header is used for all Anode packets: + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| Hop Count | 1 | 8-bit hop count (not included in MAC) | +| Flags | 1 | 8-bit flags | +| MAC | 8 | 8 byte shortened CMAC-AES of packet | +| Sender Address | ? | Full address or short ID of sender | +| Recipient Address | ? | Full address or short ID of recipient | +| Peer IDs | 1 | Two 4-bit peer IDs: sender, recipient | +| Message Type | 1 | 8-bit message type | +| Message | ? | Message payload | +|---------------------------------------------------------------------------| + +3.1. Hop Count + +The hop count begins at zero and must be incremented by each peer that relays +the packet to another peer. The hop count must not wrap to zero at 255. + +Because the hop count is modified in transit, it is not included in MAC +calculation or authentication. + +The hop count is used to prioritize endpoints that are direct over endpoints +that involve relaying, or to prioritize closer routes over more distant +ones. + +3.2. Flags and Flag Behavior + +|---------------------------------------------------------------------------| +| Flag | Description | +|---------------------------------------------------------------------------| +| 0x01 | Sender address fully specified | +| 0x02 | Recipient address fully specified | +| 0x04 | Authentication error response | +|---------------------------------------------------------------------------| + +If flag 0x01 is set, then the sender address will be the full address rather +than a short address identifier. The length of the address can be determined +from the first byte of the address, which always specifies the address type. +Flag 0x02 has the same meaning for the recipient address. + +A peer must send fully specified sender addresses until it receives a response +from the recipient. At this point the sender may assume that the recipient +knows its address and use short a short sender address instead. This +assumption should time out, with a recommended timeout of 60 seconds. + +There is presently no need to send fully specified recipient addresses, but +the flag is present in case it is needed and must be honored. + +Flag 0x04 indicates that this is an error response containing a failed +authentication error. Since authentication failed, this packet may not have +a valid MAC. Packets with this flag must never have any effect other than +to inform of an error. This error, since it is unauthenticated, must never +have any side effects such as terminating a connection. + +3.3. MAC + +The MAC is calculated as follows: + +1) Temporarily set the 64-bit/8-byte MAC field in the packet to the packet's + size as a 64-bit big-endian integer. +2) Calculate the MAC for the entire packet (excluding the first byte) using + the key agreed upon between the sender and the recipient, resulting in a + 16 byte full CMAC-AES MAC. +3) Derive the 8 byte packet MAC by XORing the first 8 bytes of the full 16 + byte CMAC-AES MAC with the last 8 bytes. Place this into the packet's MAC + field. + +3.4. Peer IDs + +Peer IDs provide a method for up to 15 different peers to share an address, +each with a unique ID allowing packets to be routed to them individually. + +A peer ID of zero indicates "any" or "unspecified." Real peers must have a +nonzero peer ID. In the normal single peer per address case, any peer ID may +be used. If multiple peers are to share an address, some implementation- +dependent method must be used to ensure that each peer has a unique peer ID. + +Relaying peers must follow these rules based on the recipient peer ID when +relaying messages: + + - IF the peer ID is zero or if the peer ID is not known, the message must + be forwarded to a random endpoint for the given recipient address. + - IF the peer ID is nonzero and matches one or more known endpoints for the + given recipient address and peer ID, the message must only be sent to + a matching endpoint. + +A receiving peer should process any message that it receives regardless of +whether its recipient peer ID is correct. The peer ID is primarily for relays. + +Peers should typically send messages with a nonzero recipient peer ID when +responding to or involved in a conversation with a specific peer (e.g. a +streaming connection), and send zero recipient peer IDs otherwise. + +3.5. Short Address Conflict Disambiguation + +In the unlikely event of two Anode addresses with the same short identifier, +the recipient should use MAC validation to disambiguate. The peer ID must not +be relied upon for this purpose. + +***************************************************************************** + +4. Basic Signaling and Transport Protocol + +4.1. Message Types + +|---------------------------------------------------------------------------| +| Type | ID | Description | +|---------------------------------------------------------------------------| +| ERROR | 0x00 | Error response | +| PING | 0x01 | Echo request | +| PONG | 0x02 | Echo response | +| EPC_REQ | 0x03 | Endpoint check request | +| EPC | 0x04 | Endpoint check response | +| EPI | 0x05 | Endpoint information | +| NAT_T | 0x06 | NAT traversal message | +| NETID_REQ | 0x07 | Request network address identification and/or test | +| NETID | 0x08 | Response to network address identification request | +| DGRAM | 0x09 | Simple UDP-like datagram | +|---------------------------------------------------------------------------| + +4.2. Message Details + +4.2.1. ERROR + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| Error Code | 2 | 16-bit error code | +| Error Arguments | ? | Error arguments, depending on error type | +|---------------------------------------------------------------------------| + +Error arguments are empty unless otherwise stated below. + +Error codes: + +|---------------------------------------------------------------------------| +| Error Code | Description | +|---------------------------------------------------------------------------| +| 0x01 | Message not valid | +| 0x02 | Message authentication or decryption failed | +| 0x03 | Relaying and related features not authorized | +| 0x04 | Relay recipient not reachable | +|---------------------------------------------------------------------------| + +Generation of errors is optional. A peer may choose to ignore invalid +messages or to throttle the sending of errors. + +4.2.2. PING + +(Payload unspecified.) + +Request echo of payload as PONG message. + +4.2.3. PONG + +(Payload unspecified.) + +Echoed payload of received PING message. + +4.2.4. EPC_REQ + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| Request ID | 4 | 32-bit request ID | +|---------------------------------------------------------------------------| + +Request echo of request ID in EPC message, used to check and learn endpoints. + +To learn a network endpoint for a peer, CHECK_REQ is sent. If CHECK is +returned with a valid request ID, the endpoint is considered valid. + +4.2.5. EPC + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| Request ID | 4 | 32-bit request ID echoed back | +|---------------------------------------------------------------------------| + +Response to EPC_REQ containing request ID. + +4.2.6. EPI + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| Flags | 1 | 8-bit flags | +| Endpoint | ? | Endpoint type and address | +| NAT-T mode | 1 | 8-bit NAT traversal mode | +| NAT-T options | ? | Options related to specified NAT-T mode | +|---------------------------------------------------------------------------| + +EPI stands for EndPoint Identification, and is sent to notify another peer of +a network endpoint where the sending peer is reachable. + +If the receiving peer is interested in communicating with the sending peer, +the receiving peer must send EPC_REQ to the sending peer at the specified +endpoint to check the validity of that endpoint. The endpoint is learned if a +valid EPC is returned. + +If the endpoint in EPI is unspecified, the actual source of the EPI message +is the endpoint. This allows EPI messages to be broadcast on a local LAN +segment to advertise the presence of an address on a local network. EPI +broadcasts on local IP networks must be made to UDP port 8737. + +Usually EPI is sent via relays (usually zone relays) to inform a peer of an +endpoint for direct communication. + +There are presently no flags, so flags must be zero. + +4.2.7. NAT_T + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| NAT-T mode | 1 | 8-bit NAT traversal mode | +| NAT-T options | ? | Options related to specified NAT-T mode | +|---------------------------------------------------------------------------| + +NAT_T is used to send messages specific to certain NAT traversal modes. + +4.2.8. NETID_REQ + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| Request ID | 4 | 32-bit request ID | +| Endpoint | ? | Endpoint type and address information | +|---------------------------------------------------------------------------| + +When a NETID_REQ message is received, the recipient attempts to echo it back +as a NETID message to the specified endpoint address. If the endpoint is +unspecified, the recipient must fill it in with the actual origin of the +NETID_REQ message. This allows a peer to cooperate with another peer (usually +a zone relay) to empirically determine its externally visible network +address information. + +A peer may ignore NETID_REQ or respond with an error if it does not allow +relaying. + +4.2.9. NETID + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| Request ID | 4 | 32-bit request ID echoed back | +| Endpoint Type | 1 | 8-bit endpoint type | +| Endpoint Address | ? | Endpoint Address (size depends on type) | +|---------------------------------------------------------------------------| + +NETID is sent in response to NETID_REQ to the specified endpoint address. It +always contains the endpoint address to which it was sent. + +4.2.10. DGRAM + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| Source Port | 2 | 16-bit source port | +| Destination Port | 2 | 16-bit destination port | +| Payload | ? | Datagram packet payload | +|---------------------------------------------------------------------------| + +A datagram is a UDP-like message without flow control or delivery assurance. + +***************************************************************************** + +5. Stream Protocol + +The stream protocol is very similar to TCP, though it omits some features +that are not required since they are taken care of by the encapsulating +protocol. SCTP was also an inspiration in the design. + +5.1. Message Types + +|---------------------------------------------------------------------------| +| Type | ID | Description | +|---------------------------------------------------------------------------| +| S_OPEN | 20 | Initiate a streaming connection (like TCP SYN) | +| S_CLOSE | 21 | Terminate a streaming connection (like TCP RST/FIN) | +| S_DATA | 22 | Data packet | +| S_ACK | 23 | Acknowedge receipt of one or more data packets | +| S_DACK | 24 | Combination of DATA and ACK | +|---------------------------------------------------------------------------| + +5.2. Message Details + +5.2.1. S_OPEN + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| Sender Link ID | 2 | 16-bit sender link ID | +| Destination Port | 2 | 16-bit destination port | +| Window Size | 2 | 16-bit window size in 1024-byte increments | +| Init. Seq. Number | 4 | 32-bit initial sequence number | +| Flags | 1 | 8-bit flags | +|---------------------------------------------------------------------------| + +The OPEN message corresponds to TCP SYN, and initiates a connection. It +specifies the initial window size for the sender and the sender's initial +sequence number, which should be randomly chosen to prevent replay attacks. + +If OPEN is successful, the recipient sends its own OPEN to establish the +connetion. If OPEN is unsuccessful, CLOSE is sent with its initial and current +sequence numbers equal and an appropriate reason such as "connection refused." + +The sender link ID must be unique for a given recipient. + +If flag 01 is set, the sender link ID is actually a source port where the +sender might be listening for connections as well. This exactly duplicates +the behavior of standard TCP. Otherwise, the sender link ID is simply an +arbitrary number that the sender uses to identify the connection with this +recipient and there is no port of origin. Ports of origin are optional for +Anode streaming connections to permit greater scalability. + +5.2.2. S_CLOSE + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| Sender Link ID | 2 | 16-bit sender link ID | +| Destination Port | 2 | 16-bit destination port | +| Flags | 1 | 8-bit flags | +| Reason | 1 | 8-bit close reason | +| Init. Seq. Number | 4 | 32-bit initial sequence number | +| Sequence Number | 4 | 32-bit current sequence number | +|---------------------------------------------------------------------------| + +The CLOSE message serves a function similar to TCP FIN. The initial sequence +number is the original starting sequence number sent with S_OPEN, while the +current sequence number is the sequence number corresponding to the close +and must be ACKed to complete the close operation. The use of the initial +sequence number helps to serve as a key to prevent replay attacks. + +CLOSE is also used to indicate a failed OPEN attempt. In this case the current +sequence number will be equal to the initial sequence number and no ACK will +be expected. + +There are currently no flags, so flags must be zero. + +The reason field describes the reason for the close: + +|---------------------------------------------------------------------------| +| Reason Code | Description | +|---------------------------------------------------------------------------| +| 00 | Application closed connection | +| 01 | Connection refused | +| 02 | Protocol error | +| 03 | Timed out | +|---------------------------------------------------------------------------| + +Established connections will usually be closed with reason 00, while reason +01 is usually provided if an OPEN is received but the port is not bound. + +5.2.3. S_DATA + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| Sender Link ID | 2 | 16-bit sender link ID | +| Destination Port | 2 | 16-bit destination port | +| Sequence Number | 4 | 32-bit sequence number | +| Payload | ? | Data payload | +|---------------------------------------------------------------------------| + +The DATA message carries a packet of data, with the sequence number +determining order. The sequence number is monotonically incremented with +each data packet, and wraps at the maximum value of an unsigned 32-bit +integer. + +5.2.4. S_ACK + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| Sender Link ID | 2 | 16-bit sender link ID | +| Destination Port | 2 | 16-bit destination port | +| Window Size | 2 | 16-bit window size in 1024-byte increments | +| Acknowledgements | ? | One or more acknowledgements (see below) | +|---------------------------------------------------------------------------| + +Each acknowledgement is a 32-bit integer followed by an 8-bit integer (5 bytes +total). The 32-bit integer is the first sequence number to acknowledge, and +the 8-bit integer is the number of sequential following sequence numbers to +acknowledge. For example "1, 4" would acknowledge sequence numbers 1, 2, 3, +and 4. + +5.2.5. S_DACK + +|---------------------------------------------------------------------------| +| Field | Length | Description | +|---------------------------------------------------------------------------| +| Sender Link ID | 2 | 16-bit sender link ID | +| Destination Port | 2 | 16-bit destination port | +| Window Size | 2 | 16-bit window size in 1024-byte increments | +| Num. Acks | 1 | 8-bit number of acknowledgements | +| Acknowledgements | ? | One or more acknowledgements | +| Payload | ? | Data payload | +|---------------------------------------------------------------------------| + +The DACK message combines ACK and DATA, allowing two peers that are both +transmitting data to efficiently ACK without a separate packet. diff --git a/attic/linux-build-farm/README.md b/attic/linux-build-farm/README.md new file mode 100644 index 0000000..8055eb0 --- /dev/null +++ b/attic/linux-build-farm/README.md @@ -0,0 +1,8 @@ +Dockerized Linux Build Farm +====== + +This subfolder contains Dockerfiles and a script to build Linux packages for a variety of Linux distributions. It's also an excellent way to test your CPU fans and stress test your disk. + +Running `build.sh` with no arguments builds everything. You can run `build.sh` with the name of a distro (e.g. centos-7) to only build that. Both 32 and 64 bit packages are built except where no 32-bit version of the distribution exists. + +The `make-apt-repos.sh` and `make-rpm-repos.sh` scripts build repositories. They may require some editing for outside-of-ZeroTier use, and be careful with the apt one if you have an existing *aptly* configuration. diff --git a/attic/linux-build-farm/amazon-2016.03/x64/Dockerfile b/attic/linux-build-farm/amazon-2016.03/x64/Dockerfile new file mode 100644 index 0000000..bd1a246 --- /dev/null +++ b/attic/linux-build-farm/amazon-2016.03/x64/Dockerfile @@ -0,0 +1,13 @@ +#FROM ambakshi/amazon-linux:2016.03 +#MAINTAINER Adam Ierymenko + +#RUN yum update -y +#RUN yum install -y epel-release +#RUN yum install -y make development-tools rpmdevtools clang gcc-c++ ruby ruby-devel + +#RUN gem install ronn + +FROM zerotier/zt1-build-amazon-2016.03-x64-base +MAINTAINER Adam Ierymenko + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/build.sh b/attic/linux-build-farm/build.sh new file mode 100755 index 0000000..0eb7c5d --- /dev/null +++ b/attic/linux-build-farm/build.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +export PATH=/bin:/usr/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin + +subdirs=$* +if [ ! -n "$subdirs" ]; then + subdirs=`find . -type d -name '*-*' -printf '%f '` +fi + +if [ ! -d ./ubuntu-trusty ]; then + echo 'Must run from linux-build-farm subfolder.' + exit 1 +fi + +rm -f zt1-src.tar.gz +cd .. +git archive --format=tar.gz --prefix=ZeroTierOne/ -o linux-build-farm/zt1-src.tar.gz HEAD +cd linux-build-farm + +# Note that --privileged is used so we can bind mount VM shares when building in a VM. +# It has no other impact or purpose, but probably doesn't matter here in any case. + +for distro in $subdirs; do + echo + echo "--- BUILDING FOR $distro ---" + echo + + cd $distro + + if [ -d x64 ]; then + cd x64 + mv ../../zt1-src.tar.gz . + docker build -t zt1-build-${distro}-x64 . + mv zt1-src.tar.gz ../.. + cd .. + fi + + if [ -d x86 ]; then + cd x86 + mv ../../zt1-src.tar.gz . + docker build -t zt1-build-${distro}-x86 . + mv zt1-src.tar.gz ../.. + cd .. + fi + + rm -f *.deb *.rpm + +# exit 0 + + if [ ! -n "`echo $distro | grep -F debian`" -a ! -n "`echo $distro | grep -F ubuntu`" ]; then + if [ -d x64 ]; then + docker run --rm -v `pwd`:/artifacts --privileged -it zt1-build-${distro}-x64 /bin/bash -c 'cd /ZeroTierOne ; make redhat ; cd .. ; cp `find /root/rpmbuild -type f -name *.rpm` /artifacts ; ls -l /artifacts' + fi + if [ -d x86 ]; then + docker run --rm -v `pwd`:/artifacts --privileged -it zt1-build-${distro}-x86 /bin/bash -c 'cd /ZeroTierOne ; make redhat ; cd .. ; cp `find /root/rpmbuild -type f -name *.rpm` /artifacts ; ls -l /artifacts' + fi + else + if [ -d x64 ]; then + docker run --rm -v `pwd`:/artifacts --privileged -it zt1-build-${distro}-x64 /bin/bash -c 'cd /ZeroTierOne ; make debian ; cd .. ; cp *.deb /artifacts ; ls -l /artifacts' + fi + if [ -d x86 ]; then + docker run --rm -v `pwd`:/artifacts --privileged -it zt1-build-${distro}-x86 /bin/bash -c 'cd /ZeroTierOne ; make debian ; cd .. ; cp *.deb /artifacts ; ls -l /artifacts' + fi + fi + + cd .. +done + +rm -f zt1-src.tar.gz diff --git a/attic/linux-build-farm/centos-6/x64/Dockerfile b/attic/linux-build-farm/centos-6/x64/Dockerfile new file mode 100644 index 0000000..2796e42 --- /dev/null +++ b/attic/linux-build-farm/centos-6/x64/Dockerfile @@ -0,0 +1,13 @@ +FROM centos:6 +MAINTAINER Adam Ierymenko + +RUN yum update -y +RUN yum install -y epel-release +RUN yum install -y make development-tools rpmdevtools clang gcc-c++ tar + +RUN yum install -y nodejs npm + +# Stop use of http-parser-devel which is installed by nodejs/npm +RUN rm -f /usr/include/http_parser.h + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/centos-6/x86/Dockerfile b/attic/linux-build-farm/centos-6/x86/Dockerfile new file mode 100644 index 0000000..8192d13 --- /dev/null +++ b/attic/linux-build-farm/centos-6/x86/Dockerfile @@ -0,0 +1,13 @@ +FROM toopher/centos-i386:centos6 +MAINTAINER Adam Ierymenko + +RUN yum update -y +RUN yum install -y epel-release +RUN yum install -y make development-tools rpmdevtools clang gcc-c++ tar + +RUN yum install -y nodejs npm + +# Stop use of http-parser-devel which is installed by nodejs/npm +RUN rm -f /usr/include/http_parser.h + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/centos-7/x64/Dockerfile b/attic/linux-build-farm/centos-7/x64/Dockerfile new file mode 100644 index 0000000..10b5840 --- /dev/null +++ b/attic/linux-build-farm/centos-7/x64/Dockerfile @@ -0,0 +1,10 @@ +FROM centos:7 +MAINTAINER Adam Ierymenko + +RUN yum update -y +RUN yum install -y epel-release +RUN yum install -y make development-tools rpmdevtools clang gcc-c++ ruby ruby-devel + +RUN gem install ronn + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/centos-7/x86/Dockerfile b/attic/linux-build-farm/centos-7/x86/Dockerfile new file mode 100644 index 0000000..a637a8d --- /dev/null +++ b/attic/linux-build-farm/centos-7/x86/Dockerfile @@ -0,0 +1,22 @@ +#FROM zerotier/centos7-32bit +#MAINTAINER Adam Ierymenko + +#RUN echo 'i686-redhat-linux' >/etc/rpm/platform + +#RUN yum update -y +#RUN yum install -y make development-tools rpmdevtools http-parser-devel lz4-devel libnatpmp-devel + +#RUN yum install -y gcc-c++ +#RUN rpm --install --force https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm +#RUN rpm --install --force ftp://rpmfind.net/linux/centos/6.8/os/i386/Packages/libffi-3.0.5-3.2.el6.i686.rpm +#RUN yum install -y clang + +FROM zerotier/zt1-build-centos-7-x86-base +MAINTAINER Adam Ierymenko + +RUN yum install -y ruby ruby-devel +RUN gem install ronn + +#RUN rpm --erase http-parser-devel lz4-devel libnatpmp-devel + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/debian-jessie/x64/Dockerfile b/attic/linux-build-farm/debian-jessie/x64/Dockerfile new file mode 100644 index 0000000..316c1d8 --- /dev/null +++ b/attic/linux-build-farm/debian-jessie/x64/Dockerfile @@ -0,0 +1,12 @@ +FROM debian:jessie +MAINTAINER Adam Ierymenko + +RUN apt-get update +RUN apt-get install -y build-essential debhelper libhttp-parser-dev liblz4-dev libnatpmp-dev dh-systemd ruby-ronn g++ make devscripts clang-3.5 + +RUN ln -sf /usr/bin/clang++-3.5 /usr/bin/clang++ +RUN ln -sf /usr/bin/clang-3.5 /usr/bin/clang + +RUN dpkg --purge libhttp-parser-dev + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/debian-jessie/x86/Dockerfile b/attic/linux-build-farm/debian-jessie/x86/Dockerfile new file mode 100644 index 0000000..3ad8332 --- /dev/null +++ b/attic/linux-build-farm/debian-jessie/x86/Dockerfile @@ -0,0 +1,12 @@ +FROM 32bit/debian:jessie +MAINTAINER Adam Ierymenko + +RUN apt-get update +RUN apt-get install -y build-essential debhelper libhttp-parser-dev liblz4-dev libnatpmp-dev dh-systemd ruby-ronn g++ make devscripts clang-3.5 + +RUN ln -sf /usr/bin/clang++-3.5 /usr/bin/clang++ +RUN ln -sf /usr/bin/clang-3.5 /usr/bin/clang + +RUN dpkg --purge libhttp-parser-dev + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/debian-stretch/x64/Dockerfile b/attic/linux-build-farm/debian-stretch/x64/Dockerfile new file mode 100644 index 0000000..c973c2b --- /dev/null +++ b/attic/linux-build-farm/debian-stretch/x64/Dockerfile @@ -0,0 +1,12 @@ +FROM debian:stretch +MAINTAINER Adam Ierymenko + +RUN apt-get update +RUN apt-get install -y build-essential debhelper libhttp-parser-dev liblz4-dev libnatpmp-dev dh-systemd ruby-ronn g++ make devscripts clang + +#RUN ln -sf /usr/bin/clang++-3.5 /usr/bin/clang++ +#RUN ln -sf /usr/bin/clang-3.5 /usr/bin/clang + +RUN dpkg --purge libhttp-parser-dev + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/debian-stretch/x86/Dockerfile b/attic/linux-build-farm/debian-stretch/x86/Dockerfile new file mode 100644 index 0000000..bfc7a86 --- /dev/null +++ b/attic/linux-build-farm/debian-stretch/x86/Dockerfile @@ -0,0 +1,12 @@ +FROM mcandre/docker-debian-32bit:stretch +MAINTAINER Adam Ierymenko + +RUN apt-get update +RUN apt-get install -y build-essential debhelper libhttp-parser-dev liblz4-dev libnatpmp-dev dh-systemd ruby-ronn g++ make devscripts clang + +#RUN ln -sf /usr/bin/clang++-3.5 /usr/bin/clang++ +#RUN ln -sf /usr/bin/clang-3.5 /usr/bin/clang + +RUN dpkg --purge libhttp-parser-dev + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/debian-wheezy/x64/Dockerfile b/attic/linux-build-farm/debian-wheezy/x64/Dockerfile new file mode 100644 index 0000000..77e1c32 --- /dev/null +++ b/attic/linux-build-farm/debian-wheezy/x64/Dockerfile @@ -0,0 +1,12 @@ +FROM debian:wheezy +MAINTAINER Adam Ierymenko + +RUN apt-get update +RUN apt-get install -y build-essential debhelper ruby-ronn g++ make devscripts + +RUN dpkg --purge libhttp-parser-dev + +ADD zt1-src.tar.gz / + +RUN mv -f /ZeroTierOne/debian/control.wheezy /ZeroTierOne/debian/control +RUN mv -f /ZeroTierOne/debian/rules.wheezy /ZeroTierOne/debian/rules diff --git a/attic/linux-build-farm/debian-wheezy/x86/Dockerfile b/attic/linux-build-farm/debian-wheezy/x86/Dockerfile new file mode 100644 index 0000000..1f0117d --- /dev/null +++ b/attic/linux-build-farm/debian-wheezy/x86/Dockerfile @@ -0,0 +1,15 @@ +#FROM tubia/debian:wheezy +#MAINTAINER Adam Ierymenko + +#RUN apt-get update +#RUN apt-get install -y build-essential debhelper ruby-ronn g++ make devscripts + +FROM zerotier/zt1-build-debian-wheezy-x86-base +MAINTAINER Adam Ierymenko + +RUN dpkg --purge libhttp-parser-dev + +ADD zt1-src.tar.gz / + +RUN mv -f /ZeroTierOne/debian/control.wheezy /ZeroTierOne/debian/control +RUN mv -f /ZeroTierOne/debian/rules.wheezy /ZeroTierOne/debian/rules diff --git a/attic/linux-build-farm/fedora-22/x64/Dockerfile b/attic/linux-build-farm/fedora-22/x64/Dockerfile new file mode 100644 index 0000000..6da0a92 --- /dev/null +++ b/attic/linux-build-farm/fedora-22/x64/Dockerfile @@ -0,0 +1,10 @@ +FROM fedora:22 +MAINTAINER Adam Ierymenko + +RUN yum update -y +RUN yum install -y make rpmdevtools gcc-c++ rubygem-ronn json-parser-devel lz4-devel http-parser-devel libnatpmp-devel + +RUN rpm --erase http-parser-devel +RUN yum install -y rubygem-ronn ruby + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/fedora-22/x86/Dockerfile b/attic/linux-build-farm/fedora-22/x86/Dockerfile new file mode 100644 index 0000000..3c24b84 --- /dev/null +++ b/attic/linux-build-farm/fedora-22/x86/Dockerfile @@ -0,0 +1,19 @@ +#FROM nickcis/fedora-32:22 +#MAINTAINER Adam Ierymenko + +#RUN mkdir -p /etc/dnf/vars +#RUN echo 'i386' >/etc/dnf/vars/basearch +#RUN echo 'i386' >/etc/dnf/vars/arch + +#RUN yum update -y +#RUN yum install -y make rpmdevtools gcc-c++ rubygem-ronn json-parser-devel lz4-devel http-parser-devel libnatpmp-devel + +FROM zerotier/zt1-build-fedora-22-x86-base +MAINTAINER Adam Ierymenko + +RUN echo 'i686-redhat-linux' >/etc/rpm/platform + +RUN rpm --erase http-parser-devel +RUN yum install -y rubygem-ronn ruby + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/make-apt-repos.sh b/attic/linux-build-farm/make-apt-repos.sh new file mode 100755 index 0000000..7a81cc5 --- /dev/null +++ b/attic/linux-build-farm/make-apt-repos.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# This builds a series of Debian repositories for each distribution. + +export PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin + +for distro in debian-* ubuntu-*; do + if [ -n "`find ${distro} -name '*.deb' -type f`" ]; then + arches=`ls ${distro}/*.deb | cut -d _ -f 3 | cut -d . -f 1 | xargs | sed 's/ /,/g'` + distro_name=`echo $distro | cut -d '-' -f 2` + echo '---' $distro / $distro_name / $arches + aptly repo create -architectures=${arches} -comment="ZeroTier, Inc. Debian Packages" -component="main" -distribution=${distro_name} zt-release-${distro_name} + aptly repo add zt-release-${distro_name} ${distro}/*.deb + aptly publish repo zt-release-${distro_name} $distro_name + fi +done diff --git a/attic/linux-build-farm/make-rpm-repos.sh b/attic/linux-build-farm/make-rpm-repos.sh new file mode 100755 index 0000000..0ed1cfe --- /dev/null +++ b/attic/linux-build-farm/make-rpm-repos.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +export PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin + +GPG_KEY=contact@zerotier.com + +rm -rf /tmp/zt-rpm-repo +mkdir /tmp/zt-rpm-repo + +for distro in centos-* fedora-* amazon-*; do + dname=`echo $distro | cut -d '-' -f 1` + if [ "$dname" = "centos" ]; then + dname=el + fi + if [ "$dname" = "fedora" ]; then + dname=fc + fi + if [ "$dname" = "amazon" ]; then + dname=amzn1 + fi + dvers=`echo $distro | cut -d '-' -f 2` + + mkdir -p /tmp/zt-rpm-repo/$dname/$dvers + + cp -v $distro/*.rpm /tmp/zt-rpm-repo/$dname/$dvers +done + +rpmsign --resign --key-id=$GPG_KEY --digest-algo=sha256 `find /tmp/zt-rpm-repo -type f -name '*.rpm'` + +for db in `find /tmp/zt-rpm-repo -mindepth 2 -maxdepth 2 -type d`; do + createrepo --database $db +done + +# Stupid RHEL stuff +cd /tmp/zt-rpm-repo/el +ln -sf 6 6Client +ln -sf 6 6Workstation +ln -sf 6 6Server +ln -sf 6 6.0 +ln -sf 6 6.1 +ln -sf 6 6.2 +ln -sf 6 6.3 +ln -sf 6 6.4 +ln -sf 6 6.5 +ln -sf 6 6.6 +ln -sf 6 6.7 +ln -sf 6 6.8 +ln -sf 6 6.9 +ln -sf 7 7Client +ln -sf 7 7Workstation +ln -sf 7 7Server +ln -sf 7 7.0 +ln -sf 7 7.1 +ln -sf 7 7.2 +ln -sf 7 7.3 +ln -sf 7 7.4 +ln -sf 7 7.5 +ln -sf 7 7.6 +ln -sf 7 7.7 +ln -sf 7 7.8 +ln -sf 7 7.9 + +echo +echo Repo created in /tmp/zt-rpm-repo diff --git a/attic/linux-build-farm/ubuntu-trusty/x64/Dockerfile b/attic/linux-build-farm/ubuntu-trusty/x64/Dockerfile new file mode 100644 index 0000000..f84cc6e --- /dev/null +++ b/attic/linux-build-farm/ubuntu-trusty/x64/Dockerfile @@ -0,0 +1,12 @@ +FROM ubuntu:14.04 +MAINTAINER Adam Ierymenko + +RUN apt-get update +RUN apt-get install -y build-essential debhelper libhttp-parser-dev liblz4-dev libnatpmp-dev dh-systemd ruby-ronn g++ make devscripts clang-3.6 + +RUN ln -sf /usr/bin/clang++-3.6 /usr/bin/clang++ +RUN ln -sf /usr/bin/clang-3.6 /usr/bin/clang + +RUN dpkg --purge libhttp-parser-dev + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/ubuntu-trusty/x86/Dockerfile b/attic/linux-build-farm/ubuntu-trusty/x86/Dockerfile new file mode 100644 index 0000000..6be3ae8 --- /dev/null +++ b/attic/linux-build-farm/ubuntu-trusty/x86/Dockerfile @@ -0,0 +1,12 @@ +FROM 32bit/ubuntu:14.04 +MAINTAINER Adam Ierymenko + +RUN apt-get update +RUN apt-get install -y build-essential debhelper libhttp-parser-dev liblz4-dev libnatpmp-dev dh-systemd ruby-ronn g++ make devscripts clang-3.6 + +RUN ln -sf /usr/bin/clang++-3.6 /usr/bin/clang++ +RUN ln -sf /usr/bin/clang-3.6 /usr/bin/clang + +RUN dpkg --purge libhttp-parser-dev + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/ubuntu-wily/x64/Dockerfile b/attic/linux-build-farm/ubuntu-wily/x64/Dockerfile new file mode 100644 index 0000000..99b8d34 --- /dev/null +++ b/attic/linux-build-farm/ubuntu-wily/x64/Dockerfile @@ -0,0 +1,12 @@ +FROM ubuntu:wily +MAINTAINER Adam Ierymenko + +RUN apt-get update +RUN apt-get install -y build-essential debhelper libhttp-parser-dev liblz4-dev libnatpmp-dev dh-systemd ruby-ronn g++ make devscripts clang-3.7 + +RUN ln -sf /usr/bin/clang++-3.7 /usr/bin/clang++ +RUN ln -sf /usr/bin/clang-3.7 /usr/bin/clang + +RUN dpkg --purge libhttp-parser-dev + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/ubuntu-wily/x86/Dockerfile b/attic/linux-build-farm/ubuntu-wily/x86/Dockerfile new file mode 100644 index 0000000..86ad14f --- /dev/null +++ b/attic/linux-build-farm/ubuntu-wily/x86/Dockerfile @@ -0,0 +1,12 @@ +FROM daald/ubuntu32:wily +MAINTAINER Adam Ierymenko + +RUN apt-get update +RUN apt-get install -y build-essential debhelper libhttp-parser-dev liblz4-dev libnatpmp-dev dh-systemd ruby-ronn g++ make devscripts clang-3.7 + +RUN ln -sf /usr/bin/clang++-3.7 /usr/bin/clang++ +RUN ln -sf /usr/bin/clang-3.7 /usr/bin/clang + +RUN dpkg --purge libhttp-parser-dev + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/ubuntu-xenial/x64/Dockerfile b/attic/linux-build-farm/ubuntu-xenial/x64/Dockerfile new file mode 100644 index 0000000..fa665a0 --- /dev/null +++ b/attic/linux-build-farm/ubuntu-xenial/x64/Dockerfile @@ -0,0 +1,14 @@ +FROM ubuntu:xenial +MAINTAINER Adam Ierymenko + +RUN apt-get update +RUN apt-get install -y build-essential debhelper libhttp-parser-dev liblz4-dev libnatpmp-dev dh-systemd ruby-ronn g++ make devscripts clang-3.8 + +#RUN ln -sf /usr/bin/clang++-3.8 /usr/bin/clang++ +#RUN ln -sf /usr/bin/clang-3.8 /usr/bin/clang + +RUN rm -f /usr/bin/clang++ /usr/bin/clang + +RUN dpkg --purge libhttp-parser-dev + +ADD zt1-src.tar.gz / diff --git a/attic/linux-build-farm/ubuntu-xenial/x86/Dockerfile b/attic/linux-build-farm/ubuntu-xenial/x86/Dockerfile new file mode 100644 index 0000000..d01eec9 --- /dev/null +++ b/attic/linux-build-farm/ubuntu-xenial/x86/Dockerfile @@ -0,0 +1,14 @@ +FROM f69m/ubuntu32:xenial +MAINTAINER Adam Ierymenko + +RUN apt-get update +RUN apt-get install -y build-essential debhelper libhttp-parser-dev liblz4-dev libnatpmp-dev dh-systemd ruby-ronn g++ make devscripts clang-3.8 + +#RUN ln -sf /usr/bin/clang++-3.8 /usr/bin/clang++ +#RUN ln -sf /usr/bin/clang-3.8 /usr/bin/clang + +RUN rm -f /usr/bin/clang++ /usr/bin/clang + +RUN dpkg --purge libhttp-parser-dev + +ADD zt1-src.tar.gz / diff --git a/attic/old-controller-schema.sql b/attic/old-controller-schema.sql new file mode 100644 index 0000000..d1daf8d --- /dev/null +++ b/attic/old-controller-schema.sql @@ -0,0 +1,112 @@ +CREATE TABLE Config ( + k varchar(16) PRIMARY KEY NOT NULL, + v varchar(1024) NOT NULL +); + +CREATE TABLE Network ( + id char(16) PRIMARY KEY NOT NULL, + name varchar(128) NOT NULL, + private integer NOT NULL DEFAULT(1), + enableBroadcast integer NOT NULL DEFAULT(1), + allowPassiveBridging integer NOT NULL DEFAULT(0), + multicastLimit integer NOT NULL DEFAULT(32), + creationTime integer NOT NULL DEFAULT(0), + revision integer NOT NULL DEFAULT(1), + memberRevisionCounter integer NOT NULL DEFAULT(1), + flags integer NOT NULL DEFAULT(0) +); + +CREATE TABLE AuthToken ( + id integer PRIMARY KEY NOT NULL, + networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE, + authMode integer NOT NULL DEFAULT(1), + useCount integer NOT NULL DEFAULT(0), + maxUses integer NOT NULL DEFAULT(0), + expiresAt integer NOT NULL DEFAULT(0), + token varchar(256) NOT NULL +); + +CREATE INDEX AuthToken_networkId_token ON AuthToken(networkId,token); + +CREATE TABLE Node ( + id char(10) PRIMARY KEY NOT NULL, + identity varchar(4096) NOT NULL +); + +CREATE TABLE IpAssignment ( + networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE, + nodeId char(10) REFERENCES Node(id) ON DELETE CASCADE, + type integer NOT NULL DEFAULT(0), + ip blob(16) NOT NULL, + ipNetmaskBits integer NOT NULL DEFAULT(0), + ipVersion integer NOT NULL DEFAULT(4) +); + +CREATE UNIQUE INDEX IpAssignment_networkId_ip ON IpAssignment (networkId, ip); + +CREATE INDEX IpAssignment_networkId_nodeId ON IpAssignment (networkId, nodeId); + +CREATE TABLE IpAssignmentPool ( + networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE, + ipRangeStart blob(16) NOT NULL, + ipRangeEnd blob(16) NOT NULL, + ipVersion integer NOT NULL DEFAULT(4) +); + +CREATE UNIQUE INDEX IpAssignmentPool_networkId_ipRangeStart ON IpAssignmentPool (networkId,ipRangeStart); + +CREATE TABLE Member ( + networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE, + nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE, + authorized integer NOT NULL DEFAULT(0), + activeBridge integer NOT NULL DEFAULT(0), + memberRevision integer NOT NULL DEFAULT(0), + flags integer NOT NULL DEFAULT(0), + lastRequestTime integer NOT NULL DEFAULT(0), + lastPowDifficulty integer NOT NULL DEFAULT(0), + lastPowTime integer NOT NULL DEFAULT(0), + recentHistory blob, + PRIMARY KEY (networkId, nodeId) +); + +CREATE INDEX Member_networkId_nodeId ON Member(networkId,nodeId); +CREATE INDEX Member_networkId_activeBridge ON Member(networkId, activeBridge); +CREATE INDEX Member_networkId_memberRevision ON Member(networkId, memberRevision); +CREATE INDEX Member_networkId_lastRequestTime ON Member(networkId, lastRequestTime); + +CREATE TABLE Route ( + networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE, + target blob(16) NOT NULL, + via blob(16), + targetNetmaskBits integer NOT NULL, + ipVersion integer NOT NULL, + flags integer NOT NULL, + metric integer NOT NULL +); + +CREATE INDEX Route_networkId ON Route (networkId); + +CREATE TABLE Rule ( + networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE, + capId integer, + ruleNo integer NOT NULL, + ruleType integer NOT NULL DEFAULT(0), + "addr" blob(16), + "int1" integer, + "int2" integer, + "int3" integer, + "int4" integer +); + +CREATE INDEX Rule_networkId_capId ON Rule (networkId,capId); + +CREATE TABLE MemberTC ( + networkId char(16) NOT NULL REFERENCES Network(id) ON DELETE CASCADE, + nodeId char(10) NOT NULL REFERENCES Node(id) ON DELETE CASCADE, + tagId integer, + tagValue integer, + capId integer, + capMaxCustodyChainLength integer NOT NULL DEFAULT(1) +); + +CREATE INDEX MemberTC_networkId_nodeId ON MemberTC (networkId,nodeId); diff --git a/attic/old-linux-installer/buildinstaller.sh b/attic/old-linux-installer/buildinstaller.sh new file mode 100755 index 0000000..21f2f73 --- /dev/null +++ b/attic/old-linux-installer/buildinstaller.sh @@ -0,0 +1,134 @@ +#!/bin/bash + +# This script builds the installer for *nix systems. Windows must do everything +# completely differently, as usual. + +export PATH=/bin:/usr/bin:/sbin:/usr/sbin + +if [ ! -f zerotier-one ]; then + echo "Could not find 'zerotier-one' binary, please build before running this script." + exit 2 +fi + +machine=`uname -m` +system=`uname -s` + +vmajor=`cat version.h | grep -F ZEROTIER_ONE_VERSION_MAJOR | cut -d ' ' -f 3` +vminor=`cat version.h | grep -F ZEROTIER_ONE_VERSION_MINOR | cut -d ' ' -f 3` +revision=`cat version.h | grep -F ZEROTIER_ONE_VERSION_REVISION | cut -d ' ' -f 3` + +if [ -z "$vmajor" -o -z "$vminor" -o -z "$revision" ]; then + echo "Unable to extract version info from version.h, aborting installer build." + exit 2 +fi + +rm -rf build-installer +mkdir build-installer + +case "$system" in + + Linux) + # Canonicalize $machine for some architectures... we use x86 + # and x64 for Intel stuff. ARM and others should be fine if + # we ever ship officially for those. + debian_arch=$machine + case "$machine" in + i386|i486|i586|i686) + machine="x86" + debian_arch="i386" + ;; + x86_64|amd64|x64) + machine="x64" + debian_arch="amd64" + ;; + armv6l|arm|armhf|arm7l|armv7l) + machine="armv6l" + debian_arch="armhf" + ;; + esac + + echo "Assembling Linux installer for $machine and version $vmajor.$vminor.$revision" + + mkdir -p 'build-installer/var/lib/zerotier-one/ui' + cp -fp 'ext/installfiles/linux/uninstall.sh' 'build-installer/var/lib/zerotier-one' + cp -fp 'zerotier-one' 'build-installer/var/lib/zerotier-one' + for f in ui/*.html ui/*.js ui/*.css ui/*.jsx ; do + cp -fp "$f" 'build-installer/var/lib/zerotier-one/ui' + done + mkdir -p 'build-installer/tmp' + cp -fp 'ext/installfiles/linux/init.d/zerotier-one' 'build-installer/tmp/init.d_zerotier-one' + cp -fp 'ext/installfiles/linux/systemd/zerotier-one.service' 'build-installer/tmp/systemd_zerotier-one.service' + + targ="ZeroTierOneInstaller-linux-${machine}-${vmajor}_${vminor}_${revision}" + # Use gzip in Linux since some minimal Linux systems do not have bunzip2 + rm -f build-installer-tmp.tar.gz + cd build-installer + tar -cf - * | gzip -9 >../build-installer-tmp.tar.gz + cd .. + rm -f $targ + cat ext/installfiles/linux/install.tmpl.sh build-installer-tmp.tar.gz >$targ + chmod 0755 $targ + rm -f build-installer-tmp.tar.gz + ls -l $targ + + if [ -f /usr/bin/dpkg-deb -a "$UID" -eq 0 ]; then + echo + echo Found dpkg-deb and you are root, trying to build Debian package. + + rm -rf build-installer-deb + + debbase="build-installer-deb/zerotier-one_${vmajor}.${vminor}.${revision}_$debian_arch" + debfolder="${debbase}/DEBIAN" + mkdir -p $debfolder + + cat 'ext/installfiles/linux/DEBIAN/control.in' | sed "s/__VERSION__/${vmajor}.${vminor}.${revision}/" | sed "s/__ARCH__/${debian_arch}/" >$debfolder/control + cat $debfolder/control + cp -f 'ext/installfiles/linux/DEBIAN/conffiles' "${debfolder}/conffiles" + + mkdir -p "${debbase}/var/lib/zerotier-one/updates.d" + cp -f $targ "${debbase}/var/lib/zerotier-one/updates.d" + + rm -f "${debfolder}/postinst" "${debfolder}/prerm" + + echo '#!/bin/bash' >${debfolder}/postinst + echo "/var/lib/zerotier-one/updates.d/${targ} >>/dev/null 2>&1" >>${debfolder}/postinst + echo "/bin/rm -f /var/lib/zerotier-one/updates.d/*" >>${debfolder}/postinst + chmod a+x ${debfolder}/postinst + + echo '#!/bin/bash' >${debfolder}/prerm + echo 'export PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin' >>${debfolder}/prerm + echo 'if [ "$1" != "upgrade" ]; then' >>${debfolder}/prerm + echo ' /var/lib/zerotier-one/uninstall.sh >>/dev/null 2>&1' >>${debfolder}/prerm + echo 'fi' >>${debfolder}/prerm + chmod a+x ${debfolder}/prerm + + dpkg-deb --build $debbase + + mv -f build-installer-deb/*.deb . + rm -rf build-installer-deb + fi + + if [ -f /usr/bin/rpmbuild ]; then + echo + echo Found rpmbuild, trying to build RedHat/CentOS package. + + rm -f /tmp/zerotier-one.spec + curr_dir=`pwd` + cat ext/installfiles/linux/RPM/zerotier-one.spec.in | sed "s/__VERSION__/${vmajor}.${vminor}.${revision}/g" | sed "s/__INSTALLER__/${targ}/g" >/tmp/zerotier-one.spec + + rpmbuild -ba /tmp/zerotier-one.spec + + rm -f /tmp/zerotier-one.spec + fi + + ;; + + *) + echo "Unsupported platform: $system" + exit 2 + +esac + +rm -rf build-installer + +exit 0 diff --git a/attic/old-linux-installer/install.tmpl.sh b/attic/old-linux-installer/install.tmpl.sh new file mode 100644 index 0000000..2d18d24 --- /dev/null +++ b/attic/old-linux-installer/install.tmpl.sh @@ -0,0 +1,182 @@ +#!/bin/bash + +export PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin +shopt -s expand_aliases + +dryRun=0 + +echo "*** ZeroTier One install/update ***" +echo + +if [ "$UID" -ne 0 ]; then + echo "Not running as root so doing dry run (no modifications to system)..." + dryRun=1 +fi + +if [ $dryRun -gt 0 ]; then + alias ln="echo '>> ln'" + alias rm="echo '>> rm'" + alias mv="echo '>> mv'" + alias cp="echo '>> cp'" + alias chown="echo '>> chown'" + alias chgrp="echo '>> chgrp'" + alias chmod="echo '>> chmod'" + alias chkconfig="echo '>> chkconfig'" + alias zerotier-cli="echo '>> zerotier-cli'" + alias service="echo '>> service'" + alias systemctl="echo '>> systemctl'" +fi + +scriptPath="`dirname "$0"`/`basename "$0"`" +if [ ! -r "$scriptPath" ]; then + scriptPath="$0" + if [ ! -r "$scriptPath" ]; then + echo "Installer cannot determine its own path; $scriptPath is not readable." + exit 2 + fi +fi + +# Check for systemd vs. old school SysV init +SYSTEMDUNITDIR= +if [ -e /bin/systemctl -o -e /usr/bin/systemctl -o -e /usr/local/bin/systemctl -o -e /sbin/systemctl -o -e /usr/sbin/systemctl ]; then + # Second check: test if systemd appears to actually be running. Apparently Ubuntu + # thought it was a good idea to ship with systemd installed but not used. Issue #133 + if [ -d /var/run/systemd/system -o -d /run/systemd/system ]; then + if [ -e /usr/bin/pkg-config ]; then + SYSTEMDUNITDIR=`/usr/bin/pkg-config systemd --variable=systemdsystemunitdir` + fi + if [ -z "$SYSTEMDUNITDIR" -o ! -d "$SYSTEMDUNITDIR" ]; then + if [ -d /usr/lib/systemd/system ]; then + SYSTEMDUNITDIR=/usr/lib/systemd/system + fi + if [ -d /etc/systemd/system ]; then + SYSTEMDUNITDIR=/etc/systemd/system + fi + fi + fi +fi + +# Find the end of this script, which is where we have appended binary data. +endMarkerIndex=`grep -a -b -E '^################' "$scriptPath" | head -c 16 | cut -d : -f 1` +if [ "$endMarkerIndex" -le 100 ]; then + echo 'Internal error: unable to find end of script / start of binary data marker.' + exit 2 +fi +blobStart=`expr $endMarkerIndex + 17` +if [ "$blobStart" -le "$endMarkerIndex" ]; then + echo 'Internal error: unable to find end of script / start of binary data marker.' + exit 2 +fi + +echo -n 'Getting version of existing install... ' +origVersion=NONE +if [ -x /var/lib/zerotier-one/zerotier-one ]; then + origVersion=`/var/lib/zerotier-one/zerotier-one -v` +fi +echo $origVersion + +echo 'Extracting files...' +if [ $dryRun -gt 0 ]; then + echo ">> tail -c +$blobStart \"$scriptPath\" | gunzip -c | tar -xvop -C / -f -" + tail -c +$blobStart "$scriptPath" | gunzip -c | tar -t -f - | sed 's/^/>> /' +else + tail -c +$blobStart "$scriptPath" | gunzip -c | tar -xvop --no-overwrite-dir -C / -f - +fi + +if [ $dryRun -eq 0 -a ! -x "/var/lib/zerotier-one/zerotier-one" ]; then + echo 'Archive extraction failed, cannot find zerotier-one binary in "/var/lib/zerotier-one".' + exit 2 +fi + +echo -n 'Getting version of new install... ' +newVersion=`/var/lib/zerotier-one/zerotier-one -v` +echo $newVersion + +echo 'Creating symlinks...' + +rm -f /usr/bin/zerotier-cli /usr/bin/zerotier-idtool +ln -sf /var/lib/zerotier-one/zerotier-one /usr/bin/zerotier-cli +ln -sf /var/lib/zerotier-one/zerotier-one /usr/bin/zerotier-idtool + +echo 'Installing zerotier-one service...' + +if [ -n "$SYSTEMDUNITDIR" -a -d "$SYSTEMDUNITDIR" ]; then + # SYSTEMD + + # If this was updated or upgraded from an init.d based system, clean up the old + # init.d stuff before installing directly via systemd. + if [ -f /etc/init.d/zerotier-one ]; then + if [ -e /sbin/chkconfig -o -e /usr/sbin/chkconfig -o -e /bin/chkconfig -o -e /usr/bin/chkconfig ]; then + chkconfig zerotier-one off + fi + rm -f /etc/init.d/zerotier-one + fi + + cp -f /tmp/systemd_zerotier-one.service "$SYSTEMDUNITDIR/zerotier-one.service" + chown 0 "$SYSTEMDUNITDIR/zerotier-one.service" + chgrp 0 "$SYSTEMDUNITDIR/zerotier-one.service" + chmod 0644 "$SYSTEMDUNITDIR/zerotier-one.service" + rm -f /tmp/systemd_zerotier-one.service /tmp/init.d_zerotier-one + + systemctl enable zerotier-one.service + + echo + echo 'Done! Installed and service configured to start at system boot.' + echo + echo "To start now or restart the service if it's already running:" + echo ' sudo systemctl restart zerotier-one.service' +else + # SYSV INIT -- also covers upstart which supports SysVinit backward compatibility + + cp -f /tmp/init.d_zerotier-one /etc/init.d/zerotier-one + chmod 0755 /etc/init.d/zerotier-one + rm -f /tmp/systemd_zerotier-one.service /tmp/init.d_zerotier-one + + if [ -f /sbin/chkconfig -o -f /usr/sbin/chkconfig -o -f /usr/bin/chkconfig -o -f /bin/chkconfig ]; then + chkconfig zerotier-one on + else + # Yes Virginia, some systems lack chkconfig. + if [ -d /etc/rc0.d ]; then + rm -f /etc/rc0.d/???zerotier-one + ln -sf /etc/init.d/zerotier-one /etc/rc0.d/K89zerotier-one + fi + if [ -d /etc/rc1.d ]; then + rm -f /etc/rc1.d/???zerotier-one + ln -sf /etc/init.d/zerotier-one /etc/rc1.d/K89zerotier-one + fi + if [ -d /etc/rc2.d ]; then + rm -f /etc/rc2.d/???zerotier-one + ln -sf /etc/init.d/zerotier-one /etc/rc2.d/S11zerotier-one + fi + if [ -d /etc/rc3.d ]; then + rm -f /etc/rc3.d/???zerotier-one + ln -sf /etc/init.d/zerotier-one /etc/rc3.d/S11zerotier-one + fi + if [ -d /etc/rc4.d ]; then + rm -f /etc/rc4.d/???zerotier-one + ln -sf /etc/init.d/zerotier-one /etc/rc4.d/S11zerotier-one + fi + if [ -d /etc/rc5.d ]; then + rm -f /etc/rc5.d/???zerotier-one + ln -sf /etc/init.d/zerotier-one /etc/rc5.d/S11zerotier-one + fi + if [ -d /etc/rc6.d ]; then + rm -f /etc/rc6.d/???zerotier-one + ln -sf /etc/init.d/zerotier-one /etc/rc6.d/K89zerotier-one + fi + fi + + echo + echo 'Done! Installed and service configured to start at system boot.' + echo + echo "To start now or restart the service if it's already running:" + echo ' sudo service zerotier-one restart' +fi + +exit 0 + +# Do not remove the last line or add a carriage return to it! The installer +# looks for an unterminated line beginning with 16 #'s in itself to find +# the binary blob data, which is appended after it. + +################ \ No newline at end of file diff --git a/attic/old-linux-installer/uninstall.sh b/attic/old-linux-installer/uninstall.sh new file mode 100755 index 0000000..d9495a1 --- /dev/null +++ b/attic/old-linux-installer/uninstall.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +export PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin + +if [ "$UID" -ne 0 ]; then + echo "Must be run as root; try: sudo $0" + exit 1 +fi + +# Detect systemd vs. regular init +SYSTEMDUNITDIR= +if [ -e /bin/systemctl -o -e /usr/bin/systemctl -o -e /usr/local/bin/systemctl -o -e /sbin/systemctl -o -e /usr/sbin/systemctl ]; then + if [ -e /usr/bin/pkg-config ]; then + SYSTEMDUNITDIR=`/usr/bin/pkg-config systemd --variable=systemdsystemunitdir` + fi + if [ -z "$SYSTEMDUNITDIR" -o ! -d "$SYSTEMDUNITDIR" ]; then + if [ -d /usr/lib/systemd/system ]; then + SYSTEMDUNITDIR=/usr/lib/systemd/system + fi + if [ -d /etc/systemd/system ]; then + SYSTEMDUNITDIR=/etc/systemd/system + fi + fi +fi + +echo "Killing any running zerotier-one service..." +if [ -n "$SYSTEMDUNITDIR" -a -d "$SYSTEMDUNITDIR" ]; then + systemctl stop zerotier-one.service + systemctl disable zerotier-one.service +else + if [ -f /sbin/service -o -f /usr/sbin/service -o -f /bin/service -o -f /usr/bin/service ]; then + service zerotier-one stop + fi +fi + +sleep 1 +if [ -f /var/lib/zerotier-one/zerotier-one.pid ]; then + kill -TERM `cat /var/lib/zerotier-one/zerotier-one.pid` + sleep 1 +fi +if [ -f /var/lib/zerotier-one/zerotier-one.pid ]; then + kill -KILL `cat /var/lib/zerotier-one/zerotier-one.pid` +fi + +if [ -f /etc/init.d/zerotier-one ]; then + echo "Removing SysV init items..." + if [ -f /sbin/chkconfig -o -f /usr/sbin/chkconfig -o -f /bin/chkconfig -o -f /usr/bin/chkconfig ]; then + chkconfig zerotier-one off + fi + rm -f /etc/init.d/zerotier-one + find /etc/rc*.d -type f -name '???zerotier-one' -print0 | xargs -0 rm -f +fi + +if [ -n "$SYSTEMDUNITDIR" -a -d "$SYSTEMDUNITDIR" -a -f "$SYSTEMDUNITDIR/zerotier-one.service" ]; then + echo "Removing systemd service..." + rm -f "$SYSTEMDUNITDIR/zerotier-one.service" +fi + +echo "Erasing binary and support files..." +if [ -d /var/lib/zerotier-one ]; then + cd /var/lib/zerotier-one + rm -rf zerotier-one *.persist identity.public *.log *.pid *.sh updates.d networks.d iddb.d root-topology ui +fi + +echo "Erasing anything installed into system bin directories..." +rm -f /usr/local/bin/zerotier-cli /usr/bin/zerotier-cli /usr/local/bin/zerotier-idtool /usr/bin/zerotier-idtool + +echo "Done." +echo +echo "Your ZeroTier One identity is still preserved in /var/lib/zerotier-one" +echo "as identity.secret and can be manually deleted if you wish. Save it if" +echo "you wish to re-use the address of this node, as it cannot be regenerated." + +echo + +exit 0 diff --git a/attic/world/README.md b/attic/world/README.md new file mode 100644 index 0000000..dda4920 --- /dev/null +++ b/attic/world/README.md @@ -0,0 +1,7 @@ +World Definitions and Generator Code +====== + +This little bit of code is used to generate world updates. Ordinary users probably will never need this unless they want to test or experiment. + +See mkworld.cpp for documentation. To build from this directory use 'source ./build.sh'. + diff --git a/attic/world/build.sh b/attic/world/build.sh new file mode 100755 index 0000000..b783702 --- /dev/null +++ b/attic/world/build.sh @@ -0,0 +1 @@ +c++ -I.. -o mkworld ../node/C25519.cpp ../node/Salsa20.cpp ../node/SHA512.cpp ../node/Identity.cpp ../node/Utils.cpp ../node/InetAddress.cpp ../osdep/OSUtils.cpp mkworld.cpp diff --git a/attic/world/earth-2016-01-13.bin b/attic/world/earth-2016-01-13.bin new file mode 100644 index 0000000..5dea4d2 Binary files /dev/null and b/attic/world/earth-2016-01-13.bin differ diff --git a/attic/world/mkworld.cpp b/attic/world/mkworld.cpp new file mode 100644 index 0000000..e0f477b --- /dev/null +++ b/attic/world/mkworld.cpp @@ -0,0 +1,149 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +/* + * This utility makes the World from the configuration specified below. + * It probably won't be much use to anyone outside ZeroTier, Inc. except + * for testing and experimentation purposes. + * + * If you want to make your own World you must edit this file. + * + * When run, it expects two files in the current directory: + * + * previous.c25519 - key pair to sign this world (key from previous world) + * current.c25519 - key pair whose public key should be embedded in this world + * + * If these files do not exist, they are both created with the same key pair + * and a self-signed initial World is born. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace ZeroTier; + +int main(int argc,char **argv) +{ + std::string previous,current; + if ((!OSUtils::readFile("previous.c25519",previous))||(!OSUtils::readFile("current.c25519",current))) { + C25519::Pair np(C25519::generate()); + previous = std::string(); + previous.append((const char *)np.pub.data,ZT_C25519_PUBLIC_KEY_LEN); + previous.append((const char *)np.priv.data,ZT_C25519_PRIVATE_KEY_LEN); + current = previous; + OSUtils::writeFile("previous.c25519",previous); + OSUtils::writeFile("current.c25519",current); + fprintf(stderr,"INFO: created initial world keys: previous.c25519 and current.c25519 (both initially the same)"ZT_EOL_S); + } + + if ((previous.length() != (ZT_C25519_PUBLIC_KEY_LEN + ZT_C25519_PRIVATE_KEY_LEN))||(current.length() != (ZT_C25519_PUBLIC_KEY_LEN + ZT_C25519_PRIVATE_KEY_LEN))) { + fprintf(stderr,"FATAL: previous.c25519 or current.c25519 empty or invalid"ZT_EOL_S); + return 1; + } + C25519::Pair previousKP; + memcpy(previousKP.pub.data,previous.data(),ZT_C25519_PUBLIC_KEY_LEN); + memcpy(previousKP.priv.data,previous.data() + ZT_C25519_PUBLIC_KEY_LEN,ZT_C25519_PRIVATE_KEY_LEN); + C25519::Pair currentKP; + memcpy(currentKP.pub.data,current.data(),ZT_C25519_PUBLIC_KEY_LEN); + memcpy(currentKP.priv.data,current.data() + ZT_C25519_PUBLIC_KEY_LEN,ZT_C25519_PRIVATE_KEY_LEN); + + // ========================================================================= + // EDIT BELOW HERE + + std::vector roots; + + const uint64_t id = ZT_WORLD_ID_EARTH; + const uint64_t ts = 1452708876314ULL; // January 13th, 2016 + + // Alice + roots.push_back(World::Root()); + roots.back().identity = Identity("9d219039f3:0:01f0922a98e3b34ebcbff333269dc265d7a020aab69d72be4d4acc9c8c9294785771256cd1d942a90d1bd1d2dca3ea84ef7d85afe6611fb43ff0b74126d90a6e"); + roots.back().stableEndpoints.push_back(InetAddress("188.166.94.177/9993")); // Amsterdam + roots.back().stableEndpoints.push_back(InetAddress("2a03:b0c0:2:d0::7d:1/9993")); // Amsterdam + roots.back().stableEndpoints.push_back(InetAddress("154.66.197.33/9993")); // Johannesburg + roots.back().stableEndpoints.push_back(InetAddress("2c0f:f850:154:197::33/9993")); // Johannesburg + roots.back().stableEndpoints.push_back(InetAddress("159.203.97.171/9993")); // New York + roots.back().stableEndpoints.push_back(InetAddress("2604:a880:800:a1::54:6001/9993")); // New York + roots.back().stableEndpoints.push_back(InetAddress("169.57.143.104/9993")); // Sao Paolo + roots.back().stableEndpoints.push_back(InetAddress("2607:f0d0:1d01:57::2/9993")); // Sao Paolo + roots.back().stableEndpoints.push_back(InetAddress("107.170.197.14/9993")); // San Francisco + roots.back().stableEndpoints.push_back(InetAddress("2604:a880:1:20::200:e001/9993")); // San Francisco + roots.back().stableEndpoints.push_back(InetAddress("128.199.197.217/9993")); // Singapore + roots.back().stableEndpoints.push_back(InetAddress("2400:6180:0:d0::b7:4001/9993")); // Singapore + + // Bob + roots.push_back(World::Root()); + roots.back().identity = Identity("8841408a2e:0:bb1d31f2c323e264e9e64172c1a74f77899555ed10751cd56e86405cde118d02dffe555d462ccf6a85b5631c12350c8d5dc409ba10b9025d0f445cf449d92b1c"); + roots.back().stableEndpoints.push_back(InetAddress("45.32.198.130/9993")); // Dallas + roots.back().stableEndpoints.push_back(InetAddress("2001:19f0:6400:81c3:5400:00ff:fe18:1d61/9993")); // Dallas + roots.back().stableEndpoints.push_back(InetAddress("46.101.160.249/9993")); // Frankfurt + roots.back().stableEndpoints.push_back(InetAddress("2a03:b0c0:3:d0::6a:3001/9993")); // Frankfurt + roots.back().stableEndpoints.push_back(InetAddress("107.191.46.210/9993")); // Paris + roots.back().stableEndpoints.push_back(InetAddress("2001:19f0:6800:83a4::64/9993")); // Paris + roots.back().stableEndpoints.push_back(InetAddress("45.32.246.179/9993")); // Sydney + roots.back().stableEndpoints.push_back(InetAddress("2001:19f0:5800:8bf8:5400:ff:fe15:b39a/9993")); // Sydney + roots.back().stableEndpoints.push_back(InetAddress("45.32.248.87/9993")); // Tokyo + roots.back().stableEndpoints.push_back(InetAddress("2001:19f0:7000:9bc9:5400:00ff:fe15:c4f5/9993")); // Tokyo + roots.back().stableEndpoints.push_back(InetAddress("159.203.2.154/9993")); // Toronto + roots.back().stableEndpoints.push_back(InetAddress("2604:a880:cad:d0::26:7001/9993")); // Toronto + + // END WORLD DEFINITION + // ========================================================================= + + fprintf(stderr,"INFO: generating and signing id==%llu ts==%llu"ZT_EOL_S,(unsigned long long)id,(unsigned long long)ts); + + World nw = World::make(World::TYPE_PLANET,id,ts,currentKP.pub,roots,previousKP); + + Buffer outtmp; + nw.serialize(outtmp,false); + World testw; + testw.deserialize(outtmp,0); + if (testw != nw) { + fprintf(stderr,"FATAL: serialization test failed!"ZT_EOL_S); + return 1; + } + + OSUtils::writeFile("world.bin",std::string((const char *)outtmp.data(),outtmp.size())); + fprintf(stderr,"INFO: world.bin written with %u bytes of binary world data."ZT_EOL_S,outtmp.size()); + + fprintf(stdout,ZT_EOL_S); + fprintf(stdout,"#define ZT_DEFAULT_WORLD_LENGTH %u"ZT_EOL_S,outtmp.size()); + fprintf(stdout,"static const unsigned char ZT_DEFAULT_WORLD[ZT_DEFAULT_WORLD_LENGTH] = {"); + for(unsigned int i=0;i 0) + fprintf(stdout,","); + fprintf(stdout,"0x%.2x",(unsigned int)d[i]); + } + fprintf(stdout,"};"ZT_EOL_S); + + return 0; +} diff --git a/attic/world/old/earth-2015-11-16.bin b/attic/world/old/earth-2015-11-16.bin new file mode 100644 index 0000000..910ff14 Binary files /dev/null and b/attic/world/old/earth-2015-11-16.bin differ diff --git a/attic/world/old/earth-2015-11-20.bin b/attic/world/old/earth-2015-11-20.bin new file mode 100644 index 0000000..198682e Binary files /dev/null and b/attic/world/old/earth-2015-11-20.bin differ diff --git a/attic/world/old/earth-2015-12-17.bin b/attic/world/old/earth-2015-12-17.bin new file mode 100644 index 0000000..20fadb5 Binary files /dev/null and b/attic/world/old/earth-2015-12-17.bin differ diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp new file mode 100644 index 0000000..597bc9c --- /dev/null +++ b/controller/EmbeddedNetworkController.cpp @@ -0,0 +1,1854 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + */ + +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#endif +#include + +#include +#include +#include +#include +#include + +#include "../include/ZeroTierOne.h" +#include "../node/Constants.hpp" + +#include "EmbeddedNetworkController.hpp" + +#include "../node/Node.hpp" +#include "../node/Utils.hpp" +#include "../node/CertificateOfMembership.hpp" +#include "../node/NetworkConfig.hpp" +#include "../node/Dictionary.hpp" +#include "../node/InetAddress.hpp" +#include "../node/MAC.hpp" +#include "../node/Address.hpp" + +using json = nlohmann::json; + +// API version reported via JSON control plane +#define ZT_NETCONF_CONTROLLER_API_VERSION 3 + +// Number of requests to remember in member history +#define ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH 2 + +// Min duration between requests for an address/nwid combo to prevent floods +#define ZT_NETCONF_MIN_REQUEST_PERIOD 1000 + +// Nodes are considered active if they've queried in less than this long +#define ZT_NETCONF_NODE_ACTIVE_THRESHOLD (ZT_NETWORK_AUTOCONF_DELAY * 2) + +namespace ZeroTier { + +static json _renderRule(ZT_VirtualNetworkRule &rule) +{ + char tmp[128]; + json r = json::object(); + const ZT_VirtualNetworkRuleType rt = (ZT_VirtualNetworkRuleType)(rule.t & 0x3f); + + switch(rt) { + case ZT_NETWORK_RULE_ACTION_DROP: + r["type"] = "ACTION_DROP"; + break; + case ZT_NETWORK_RULE_ACTION_ACCEPT: + r["type"] = "ACTION_ACCEPT"; + break; + case ZT_NETWORK_RULE_ACTION_TEE: + r["type"] = "ACTION_TEE"; + r["address"] = Address(rule.v.fwd.address).toString(); + r["flags"] = (unsigned int)rule.v.fwd.flags; + r["length"] = (unsigned int)rule.v.fwd.length; + break; + case ZT_NETWORK_RULE_ACTION_WATCH: + r["type"] = "ACTION_WATCH"; + r["address"] = Address(rule.v.fwd.address).toString(); + r["flags"] = (unsigned int)rule.v.fwd.flags; + r["length"] = (unsigned int)rule.v.fwd.length; + break; + case ZT_NETWORK_RULE_ACTION_REDIRECT: + r["type"] = "ACTION_REDIRECT"; + r["address"] = Address(rule.v.fwd.address).toString(); + r["flags"] = (unsigned int)rule.v.fwd.flags; + break; + case ZT_NETWORK_RULE_ACTION_BREAK: + r["type"] = "ACTION_BREAK"; + break; + default: + break; + } + + if (r.size() == 0) { + switch(rt) { + case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: + r["type"] = "MATCH_SOURCE_ZEROTIER_ADDRESS"; + r["zt"] = Address(rule.v.zt).toString(); + break; + case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: + r["type"] = "MATCH_DEST_ZEROTIER_ADDRESS"; + r["zt"] = Address(rule.v.zt).toString(); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_ID: + r["type"] = "MATCH_VLAN_ID"; + r["vlanId"] = (unsigned int)rule.v.vlanId; + break; + case ZT_NETWORK_RULE_MATCH_VLAN_PCP: + r["type"] = "MATCH_VLAN_PCP"; + r["vlanPcp"] = (unsigned int)rule.v.vlanPcp; + break; + case ZT_NETWORK_RULE_MATCH_VLAN_DEI: + r["type"] = "MATCH_VLAN_DEI"; + r["vlanDei"] = (unsigned int)rule.v.vlanDei; + break; + case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: + r["type"] = "MATCH_MAC_SOURCE"; + Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]); + r["mac"] = tmp; + break; + case ZT_NETWORK_RULE_MATCH_MAC_DEST: + r["type"] = "MATCH_MAC_DEST"; + Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)rule.v.mac[0],(unsigned int)rule.v.mac[1],(unsigned int)rule.v.mac[2],(unsigned int)rule.v.mac[3],(unsigned int)rule.v.mac[4],(unsigned int)rule.v.mac[5]); + r["mac"] = tmp; + break; + case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: + r["type"] = "MATCH_IPV4_SOURCE"; + r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString(); + break; + case ZT_NETWORK_RULE_MATCH_IPV4_DEST: + r["type"] = "MATCH_IPV4_DEST"; + r["ip"] = InetAddress(&(rule.v.ipv4.ip),4,(unsigned int)rule.v.ipv4.mask).toString(); + break; + case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE: + r["type"] = "MATCH_IPV6_SOURCE"; + r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString(); + break; + case ZT_NETWORK_RULE_MATCH_IPV6_DEST: + r["type"] = "MATCH_IPV6_DEST"; + r["ip"] = InetAddress(rule.v.ipv6.ip,16,(unsigned int)rule.v.ipv6.mask).toString(); + break; + case ZT_NETWORK_RULE_MATCH_IP_TOS: + r["type"] = "MATCH_IP_TOS"; + r["mask"] = (unsigned int)rule.v.ipTos.mask; + r["start"] = (unsigned int)rule.v.ipTos.value[0]; + r["end"] = (unsigned int)rule.v.ipTos.value[1]; + break; + case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: + r["type"] = "MATCH_IP_PROTOCOL"; + r["ipProtocol"] = (unsigned int)rule.v.ipProtocol; + break; + case ZT_NETWORK_RULE_MATCH_ETHERTYPE: + r["type"] = "MATCH_ETHERTYPE"; + r["etherType"] = (unsigned int)rule.v.etherType; + break; + case ZT_NETWORK_RULE_MATCH_ICMP: + r["type"] = "MATCH_ICMP"; + r["icmpType"] = (unsigned int)rule.v.icmp.type; + if ((rule.v.icmp.flags & 0x01) != 0) + r["icmpCode"] = (unsigned int)rule.v.icmp.code; + else r["icmpCode"] = json(); + break; + case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: + r["type"] = "MATCH_IP_SOURCE_PORT_RANGE"; + r["start"] = (unsigned int)rule.v.port[0]; + r["end"] = (unsigned int)rule.v.port[1]; + break; + case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: + r["type"] = "MATCH_IP_DEST_PORT_RANGE"; + r["start"] = (unsigned int)rule.v.port[0]; + r["end"] = (unsigned int)rule.v.port[1]; + break; + case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: + r["type"] = "MATCH_CHARACTERISTICS"; + Utils::snprintf(tmp,sizeof(tmp),"%.16llx",rule.v.characteristics); + r["mask"] = tmp; + break; + case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE: + r["type"] = "MATCH_FRAME_SIZE_RANGE"; + r["start"] = (unsigned int)rule.v.frameSize[0]; + r["end"] = (unsigned int)rule.v.frameSize[1]; + break; + case ZT_NETWORK_RULE_MATCH_RANDOM: + r["type"] = "MATCH_RANDOM"; + r["probability"] = (unsigned long)rule.v.randomProbability; + break; + case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE: + r["type"] = "MATCH_TAGS_DIFFERENCE"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND: + r["type"] = "MATCH_TAGS_BITWISE_AND"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR: + r["type"] = "MATCH_TAGS_BITWISE_OR"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR: + r["type"] = "MATCH_TAGS_BITWISE_XOR"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL: + r["type"] = "MATCH_TAGS_EQUAL"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + case ZT_NETWORK_RULE_MATCH_TAG_SENDER: + r["type"] = "MATCH_TAG_SENDER"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER: + r["type"] = "MATCH_TAG_RECEIVER"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + default: + break; + } + + if (r.size() > 0) { + r["not"] = ((rule.t & 0x80) != 0); + r["or"] = ((rule.t & 0x40) != 0); + } + } + + return r; +} + +static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule) +{ + if (!r.is_object()) + return false; + + const std::string t(OSUtils::jsonString(r["type"],"")); + memset(&rule,0,sizeof(ZT_VirtualNetworkRule)); + + if (OSUtils::jsonBool(r["not"],false)) + rule.t = 0x80; + else rule.t = 0x00; + if (OSUtils::jsonBool(r["or"],false)) + rule.t |= 0x40; + + bool tag = false; + if (t == "ACTION_DROP") { + rule.t |= ZT_NETWORK_RULE_ACTION_DROP; + return true; + } else if (t == "ACTION_ACCEPT") { + rule.t |= ZT_NETWORK_RULE_ACTION_ACCEPT; + return true; + } else if (t == "ACTION_TEE") { + rule.t |= ZT_NETWORK_RULE_ACTION_TEE; + rule.v.fwd.address = Utils::hexStrToU64(OSUtils::jsonString(r["address"],"0").c_str()) & 0xffffffffffULL; + rule.v.fwd.flags = (uint32_t)(OSUtils::jsonInt(r["flags"],0ULL) & 0xffffffffULL); + rule.v.fwd.length = (uint16_t)(OSUtils::jsonInt(r["length"],0ULL) & 0xffffULL); + return true; + } else if (t == "ACTION_WATCH") { + rule.t |= ZT_NETWORK_RULE_ACTION_WATCH; + rule.v.fwd.address = Utils::hexStrToU64(OSUtils::jsonString(r["address"],"0").c_str()) & 0xffffffffffULL; + rule.v.fwd.flags = (uint32_t)(OSUtils::jsonInt(r["flags"],0ULL) & 0xffffffffULL); + rule.v.fwd.length = (uint16_t)(OSUtils::jsonInt(r["length"],0ULL) & 0xffffULL); + return true; + } else if (t == "ACTION_REDIRECT") { + rule.t |= ZT_NETWORK_RULE_ACTION_REDIRECT; + rule.v.fwd.address = Utils::hexStrToU64(OSUtils::jsonString(r["address"],"0").c_str()) & 0xffffffffffULL; + rule.v.fwd.flags = (uint32_t)(OSUtils::jsonInt(r["flags"],0ULL) & 0xffffffffULL); + return true; + } else if (t == "ACTION_BREAK") { + rule.t |= ZT_NETWORK_RULE_ACTION_BREAK; + return true; + } else if (t == "MATCH_SOURCE_ZEROTIER_ADDRESS") { + rule.t |= ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS; + rule.v.zt = Utils::hexStrToU64(OSUtils::jsonString(r["zt"],"0").c_str()) & 0xffffffffffULL; + return true; + } else if (t == "MATCH_DEST_ZEROTIER_ADDRESS") { + rule.t |= ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS; + rule.v.zt = Utils::hexStrToU64(OSUtils::jsonString(r["zt"],"0").c_str()) & 0xffffffffffULL; + return true; + } else if (t == "MATCH_VLAN_ID") { + rule.t |= ZT_NETWORK_RULE_MATCH_VLAN_ID; + rule.v.vlanId = (uint16_t)(OSUtils::jsonInt(r["vlanId"],0ULL) & 0xffffULL); + return true; + } else if (t == "MATCH_VLAN_PCP") { + rule.t |= ZT_NETWORK_RULE_MATCH_VLAN_PCP; + rule.v.vlanPcp = (uint8_t)(OSUtils::jsonInt(r["vlanPcp"],0ULL) & 0xffULL); + return true; + } else if (t == "MATCH_VLAN_DEI") { + rule.t |= ZT_NETWORK_RULE_MATCH_VLAN_DEI; + rule.v.vlanDei = (uint8_t)(OSUtils::jsonInt(r["vlanDei"],0ULL) & 0xffULL); + return true; + } else if (t == "MATCH_MAC_SOURCE") { + rule.t |= ZT_NETWORK_RULE_MATCH_MAC_SOURCE; + const std::string mac(OSUtils::jsonString(r["mac"],"0")); + Utils::unhex(mac.c_str(),(unsigned int)mac.length(),rule.v.mac,6); + return true; + } else if (t == "MATCH_MAC_DEST") { + rule.t |= ZT_NETWORK_RULE_MATCH_MAC_DEST; + const std::string mac(OSUtils::jsonString(r["mac"],"0")); + Utils::unhex(mac.c_str(),(unsigned int)mac.length(),rule.v.mac,6); + return true; + } else if (t == "MATCH_IPV4_SOURCE") { + rule.t |= ZT_NETWORK_RULE_MATCH_IPV4_SOURCE; + InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0")); + rule.v.ipv4.ip = reinterpret_cast(&ip)->sin_addr.s_addr; + rule.v.ipv4.mask = Utils::ntoh(reinterpret_cast(&ip)->sin_port) & 0xff; + if (rule.v.ipv4.mask > 32) rule.v.ipv4.mask = 32; + return true; + } else if (t == "MATCH_IPV4_DEST") { + rule.t |= ZT_NETWORK_RULE_MATCH_IPV4_DEST; + InetAddress ip(OSUtils::jsonString(r["ip"],"0.0.0.0")); + rule.v.ipv4.ip = reinterpret_cast(&ip)->sin_addr.s_addr; + rule.v.ipv4.mask = Utils::ntoh(reinterpret_cast(&ip)->sin_port) & 0xff; + if (rule.v.ipv4.mask > 32) rule.v.ipv4.mask = 32; + return true; + } else if (t == "MATCH_IPV6_SOURCE") { + rule.t |= ZT_NETWORK_RULE_MATCH_IPV6_SOURCE; + InetAddress ip(OSUtils::jsonString(r["ip"],"::0")); + memcpy(rule.v.ipv6.ip,reinterpret_cast(&ip)->sin6_addr.s6_addr,16); + rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast(&ip)->sin6_port) & 0xff; + if (rule.v.ipv6.mask > 128) rule.v.ipv6.mask = 128; + return true; + } else if (t == "MATCH_IPV6_DEST") { + rule.t |= ZT_NETWORK_RULE_MATCH_IPV6_DEST; + InetAddress ip(OSUtils::jsonString(r["ip"],"::0")); + memcpy(rule.v.ipv6.ip,reinterpret_cast(&ip)->sin6_addr.s6_addr,16); + rule.v.ipv6.mask = Utils::ntoh(reinterpret_cast(&ip)->sin6_port) & 0xff; + if (rule.v.ipv6.mask > 128) rule.v.ipv6.mask = 128; + return true; + } else if (t == "MATCH_IP_TOS") { + rule.t |= ZT_NETWORK_RULE_MATCH_IP_TOS; + rule.v.ipTos.mask = (uint8_t)(OSUtils::jsonInt(r["mask"],0ULL) & 0xffULL); + rule.v.ipTos.value[0] = (uint8_t)(OSUtils::jsonInt(r["start"],0ULL) & 0xffULL); + rule.v.ipTos.value[1] = (uint8_t)(OSUtils::jsonInt(r["end"],0ULL) & 0xffULL); + return true; + } else if (t == "MATCH_IP_PROTOCOL") { + rule.t |= ZT_NETWORK_RULE_MATCH_IP_PROTOCOL; + rule.v.ipProtocol = (uint8_t)(OSUtils::jsonInt(r["ipProtocol"],0ULL) & 0xffULL); + return true; + } else if (t == "MATCH_ETHERTYPE") { + rule.t |= ZT_NETWORK_RULE_MATCH_ETHERTYPE; + rule.v.etherType = (uint16_t)(OSUtils::jsonInt(r["etherType"],0ULL) & 0xffffULL); + return true; + } else if (t == "MATCH_ICMP") { + rule.t |= ZT_NETWORK_RULE_MATCH_ICMP; + rule.v.icmp.type = (uint8_t)(OSUtils::jsonInt(r["icmpType"],0ULL) & 0xffULL); + json &code = r["icmpCode"]; + if (code.is_null()) { + rule.v.icmp.code = 0; + rule.v.icmp.flags = 0x00; + } else { + rule.v.icmp.code = (uint8_t)(OSUtils::jsonInt(code,0ULL) & 0xffULL); + rule.v.icmp.flags = 0x01; + } + return true; + } else if (t == "MATCH_IP_SOURCE_PORT_RANGE") { + rule.t |= ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE; + rule.v.port[0] = (uint16_t)(OSUtils::jsonInt(r["start"],0ULL) & 0xffffULL); + rule.v.port[1] = (uint16_t)(OSUtils::jsonInt(r["end"],(uint64_t)rule.v.port[0]) & 0xffffULL); + return true; + } else if (t == "MATCH_IP_DEST_PORT_RANGE") { + rule.t |= ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE; + rule.v.port[0] = (uint16_t)(OSUtils::jsonInt(r["start"],0ULL) & 0xffffULL); + rule.v.port[1] = (uint16_t)(OSUtils::jsonInt(r["end"],(uint64_t)rule.v.port[0]) & 0xffffULL); + return true; + } else if (t == "MATCH_CHARACTERISTICS") { + rule.t |= ZT_NETWORK_RULE_MATCH_CHARACTERISTICS; + if (r.count("mask")) { + json &v = r["mask"]; + if (v.is_number()) { + rule.v.characteristics = v; + } else { + std::string tmp = v; + rule.v.characteristics = Utils::hexStrToU64(tmp.c_str()); + } + } + return true; + } else if (t == "MATCH_FRAME_SIZE_RANGE") { + rule.t |= ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE; + rule.v.frameSize[0] = (uint16_t)(OSUtils::jsonInt(r["start"],0ULL) & 0xffffULL); + rule.v.frameSize[1] = (uint16_t)(OSUtils::jsonInt(r["end"],(uint64_t)rule.v.frameSize[0]) & 0xffffULL); + return true; + } else if (t == "MATCH_RANDOM") { + rule.t |= ZT_NETWORK_RULE_MATCH_RANDOM; + rule.v.randomProbability = (uint32_t)(OSUtils::jsonInt(r["probability"],0ULL) & 0xffffffffULL); + return true; + } else if (t == "MATCH_TAGS_DIFFERENCE") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE; + tag = true; + } else if (t == "MATCH_TAGS_BITWISE_AND") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND; + tag = true; + } else if (t == "MATCH_TAGS_BITWISE_OR") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR; + tag = true; + } else if (t == "MATCH_TAGS_BITWISE_XOR") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR; + tag = true; + } else if (t == "MATCH_TAGS_EQUAL") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_EQUAL; + tag = true; + } else if (t == "MATCH_TAG_SENDER") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAG_SENDER; + tag = true; + } else if (t == "MATCH_TAG_RECEIVER") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAG_RECEIVER; + tag = true; + } + if (tag) { + rule.v.tag.id = (uint32_t)(OSUtils::jsonInt(r["id"],0ULL) & 0xffffffffULL); + rule.v.tag.value = (uint32_t)(OSUtils::jsonInt(r["value"],0ULL) & 0xffffffffULL); + return true; + } + + return false; +} + +EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *dbPath) : + _startTime(OSUtils::now()), + _threadsStarted(false), + _db(dbPath), + _node(node) +{ +} + +EmbeddedNetworkController::~EmbeddedNetworkController() +{ + Mutex::Lock _l(_threads_m); + if (_threadsStarted) { + for(int i=0;i<(ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT*2);++i) + _queue.post((_RQEntry *)0); + for(int i=0;i_sender = sender; + this->_signingId = signingId; +} + +void EmbeddedNetworkController::request( + uint64_t nwid, + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary &metaData) +{ + if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) + return; + + { + Mutex::Lock _l(_threads_m); + if (!_threadsStarted) { + for(int i=0;inwid = nwid; + qe->requestPacketId = requestPacketId; + qe->fromAddr = fromAddr; + qe->identity = identity; + qe->metaData = metaData; + _queue.post(qe); +} + +unsigned int EmbeddedNetworkController::handleControlPlaneHttpGET( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType) +{ + if ((path.size() > 0)&&(path[0] == "network")) { + + if ((path.size() >= 2)&&(path[1].length() == 16)) { + const uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); + char nwids[24]; + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); + + json network; + { + Mutex::Lock _l(_db_m); + network = _db.get("network",nwids); + } + if (!network.size()) + return 404; + + if (path.size() >= 3) { + + if (path[2] == "member") { + + if (path.size() >= 4) { + const uint64_t address = Utils::hexStrToU64(path[3].c_str()); + + json member; + { + Mutex::Lock _l(_db_m); + member = _db.get("network",nwids,"member",Address(address).toString()); + } + if (!member.size()) + return 404; + + _addMemberNonPersistedFields(member,OSUtils::now()); + responseBody = OSUtils::jsonDump(member); + responseContentType = "application/json"; + + return 200; + } else { + + Mutex::Lock _l(_db_m); + + responseBody = "{"; + _db.filter((std::string("network/") + nwids + "/member/"),[&responseBody](const std::string &n,const json &member) { + if ((member.is_object())&&(member.size() > 0)) { + responseBody.append((responseBody.length() == 1) ? "\"" : ",\""); + responseBody.append(OSUtils::jsonString(member["id"],"0")); + responseBody.append("\":"); + responseBody.append(OSUtils::jsonString(member["revision"],"0")); + } + return true; // never delete + }); + responseBody.push_back('}'); + responseContentType = "application/json"; + + return 200; + } + + } // else 404 + + } else { + + const uint64_t now = OSUtils::now(); + _NetworkMemberInfo nmi; + _getNetworkMemberInfo(now,nwid,nmi); + _addNetworkNonPersistedFields(network,now,nmi); + responseBody = OSUtils::jsonDump(network); + responseContentType = "application/json"; + return 200; + + } + } else if (path.size() == 1) { + + std::set networkIds; + { + Mutex::Lock _l(_db_m); + _db.filter("network/",[&networkIds](const std::string &n,const json &obj) { + if (n.length() == (16 + 8)) + networkIds.insert(n.substr(8)); + return true; // do not delete + }); + } + + responseBody.push_back('['); + for(std::set::iterator i(networkIds.begin());i!=networkIds.end();++i) { + responseBody.append((responseBody.length() == 1) ? "\"" : ",\""); + responseBody.append(*i); + responseBody.append("\""); + } + responseBody.push_back(']'); + responseContentType = "application/json"; + return 200; + + } // else 404 + + } else { + + char tmp[4096]; + Utils::snprintf(tmp,sizeof(tmp),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu\n}\n",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now()); + responseBody = tmp; + responseContentType = "application/json"; + return 200; + + } + + return 404; +} + +unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType) +{ + if (path.empty()) + return 404; + + json b; + try { + b = OSUtils::jsonParse(body); + if (!b.is_object()) { + responseBody = "{ \"message\": \"body is not a JSON object\" }"; + responseContentType = "application/json"; + return 400; + } + } catch ( ... ) { + responseBody = "{ \"message\": \"body JSON is invalid\" }"; + responseContentType = "application/json"; + return 400; + } + const uint64_t now = OSUtils::now(); + + if (path[0] == "network") { + + if ((path.size() >= 2)&&(path[1].length() == 16)) { + uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); + char nwids[24]; + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)nwid); + + if (path.size() >= 3) { + + if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) { + uint64_t address = Utils::hexStrToU64(path[3].c_str()); + char addrs[24]; + Utils::snprintf(addrs,sizeof(addrs),"%.10llx",(unsigned long long)address); + + json member; + { + Mutex::Lock _l(_db_m); + member = _db.get("network",nwids,"member",Address(address).toString()); + } + json origMember(member); // for detecting changes + _initMember(member); + + try { + if (b.count("activeBridge")) member["activeBridge"] = OSUtils::jsonBool(b["activeBridge"],false); + if (b.count("noAutoAssignIps")) member["noAutoAssignIps"] = OSUtils::jsonBool(b["noAutoAssignIps"],false); + + if (b.count("authorized")) { + const bool newAuth = OSUtils::jsonBool(b["authorized"],false); + if (newAuth != OSUtils::jsonBool(member["authorized"],false)) { + member["authorized"] = newAuth; + member[((newAuth) ? "lastAuthorizedTime" : "lastDeauthorizedTime")] = now; + + json ah; + ah["a"] = newAuth; + ah["by"] = "api"; + ah["ts"] = now; + ah["ct"] = json(); + ah["c"] = json(); + member["authHistory"].push_back(ah); + + // Member is being de-authorized, so spray Revocation objects to all online members + if (!newAuth) { + _clearNetworkMemberInfoCache(nwid); + Revocation rev((uint32_t)_node->prng(),nwid,0,now,ZT_REVOCATION_FLAG_FAST_PROPAGATE,Address(address),Revocation::CREDENTIAL_TYPE_COM); + rev.sign(_signingId); + Mutex::Lock _l(_lastRequestTime_m); + for(std::map< std::pair,uint64_t >::iterator i(_lastRequestTime.begin());i!=_lastRequestTime.end();++i) { + if ((now - i->second) < ZT_NETWORK_AUTOCONF_DELAY) + _node->ncSendRevocation(Address(i->first.first),rev); + } + } + } + } + + if (b.count("ipAssignments")) { + json &ipa = b["ipAssignments"]; + if (ipa.is_array()) { + json mipa(json::array()); + for(unsigned long i=0;i mtags; + for(unsigned long i=0;i::iterator t(mtags.begin());t!=mtags.end();++t) { + json ta = json::array(); + ta.push_back(t->first); + ta.push_back(t->second); + mtagsa.push_back(ta); + } + member["tags"] = mtagsa; + } + } + + if (b.count("capabilities")) { + json &capabilities = b["capabilities"]; + if (capabilities.is_array()) { + json mcaps = json::array(); + for(unsigned long i=0;itestId),sizeof(test->testId)); + test->credentialNetworkId = nwid; + test->ptr = (void *)this; + json hops = b["hops"]; + if (hops.is_array()) { + for(unsigned long i=0;ihops[test->hopCount].addresses[test->hops[test->hopCount].breadth++] = Utils::hexStrToU64(s.c_str()) & 0xffffffffffULL; + } + ++test->hopCount; + } else if (hops2.is_string()) { + std::string s = hops2; + test->hops[test->hopCount].addresses[test->hops[test->hopCount].breadth++] = Utils::hexStrToU64(s.c_str()) & 0xffffffffffULL; + ++test->hopCount; + } + } + } + test->reportAtEveryHop = (OSUtils::jsonBool(b["reportAtEveryHop"],true) ? 1 : 0); + + if (!test->hopCount) { + _tests.pop_back(); + responseBody = "{ \"message\": \"a test must contain at least one hop\" }"; + responseContentType = "application/json"; + return 400; + } + + test->timestamp = OSUtils::now(); + + if (_node) { + _node->circuitTestBegin((void *)0,test,&(EmbeddedNetworkController::_circuitTestCallback)); + } else { + _tests.pop_back(); + return 500; + } + + char json[512]; + Utils::snprintf(json,sizeof(json),"{\"testId\":\"%.16llx\",\"timestamp\":%llu}",test->testId,test->timestamp); + responseBody = json; + responseContentType = "application/json"; + + return 200; + + } // else 404 + + } else { + // POST to network ID + + json network; + { + Mutex::Lock _l(_db_m); + + // Magic ID ending with ______ picks a random unused network ID + if (path[1].substr(10) == "______") { + nwid = 0; + uint64_t nwidPrefix = (Utils::hexStrToU64(path[1].substr(0,10).c_str()) << 24) & 0xffffffffff000000ULL; + uint64_t nwidPostfix = 0; + for(unsigned long k=0;k<100000;++k) { // sanity limit on trials + Utils::getSecureRandom(&nwidPostfix,sizeof(nwidPostfix)); + uint64_t tryNwid = nwidPrefix | (nwidPostfix & 0xffffffULL); + if ((tryNwid & 0xffffffULL) == 0ULL) tryNwid |= 1ULL; + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",(unsigned long long)tryNwid); + if (_db.get("network",nwids).size() <= 0) { + nwid = tryNwid; + break; + } + } + if (!nwid) + return 503; + } + + network = _db.get("network",nwids); + } + json origNetwork(network); // for detecting changes + _initNetwork(network); + + try { + if (b.count("name")) network["name"] = OSUtils::jsonString(b["name"],""); + if (b.count("private")) network["private"] = OSUtils::jsonBool(b["private"],true); + if (b.count("enableBroadcast")) network["enableBroadcast"] = OSUtils::jsonBool(b["enableBroadcast"],false); + if (b.count("allowPassiveBridging")) network["allowPassiveBridging"] = OSUtils::jsonBool(b["allowPassiveBridging"],false); + if (b.count("multicastLimit")) network["multicastLimit"] = OSUtils::jsonInt(b["multicastLimit"],32ULL); + + if (b.count("v4AssignMode")) { + json nv4m; + json &v4m = b["v4AssignMode"]; + if (v4m.is_string()) { // backward compatibility + nv4m["zt"] = (OSUtils::jsonString(v4m,"") == "zt"); + } else if (v4m.is_object()) { + nv4m["zt"] = OSUtils::jsonBool(v4m["zt"],false); + } else nv4m["zt"] = false; + network["v4AssignMode"] = nv4m; + } + + if (b.count("v6AssignMode")) { + json nv6m; + json &v6m = b["v6AssignMode"]; + if (!nv6m.is_object()) nv6m = json::object(); + if (v6m.is_string()) { // backward compatibility + std::vector v6ms(OSUtils::split(OSUtils::jsonString(v6m,"").c_str(),",","","")); + std::sort(v6ms.begin(),v6ms.end()); + v6ms.erase(std::unique(v6ms.begin(),v6ms.end()),v6ms.end()); + nv6m["rfc4193"] = false; + nv6m["zt"] = false; + nv6m["6plane"] = false; + for(std::vector::iterator i(v6ms.begin());i!=v6ms.end();++i) { + if (*i == "rfc4193") + nv6m["rfc4193"] = true; + else if (*i == "zt") + nv6m["zt"] = true; + else if (*i == "6plane") + nv6m["6plane"] = true; + } + } else if (v6m.is_object()) { + if (v6m.count("rfc4193")) nv6m["rfc4193"] = OSUtils::jsonBool(v6m["rfc4193"],false); + if (v6m.count("zt")) nv6m["zt"] = OSUtils::jsonBool(v6m["zt"],false); + if (v6m.count("6plane")) nv6m["6plane"] = OSUtils::jsonBool(v6m["6plane"],false); + } else { + nv6m["rfc4193"] = false; + nv6m["zt"] = false; + nv6m["6plane"] = false; + } + network["v6AssignMode"] = nv6m; + } + + if (b.count("routes")) { + json &rts = b["routes"]; + if (rts.is_array()) { + json nrts = json::array(); + for(unsigned long i=0;i()); + InetAddress v; + if (via.is_string()) v.fromString(via.get()); + if ( ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) && (t.netmaskBitsValid()) ) { + json tmp; + tmp["target"] = t.toString(); + if (v.ss_family == t.ss_family) + tmp["via"] = v.toIpString(); + else tmp["via"] = json(); + nrts.push_back(tmp); + } + } + } + } + network["routes"] = nrts; + } + } + + if (b.count("ipAssignmentPools")) { + json &ipp = b["ipAssignmentPools"]; + if (ipp.is_array()) { + json nipp = json::array(); + for(unsigned long i=0;i 0) { + json t = json::object(); + t["token"] = tstr; + t["expires"] = OSUtils::jsonInt(token["expires"],0ULL); + t["maxUsesPerMember"] = OSUtils::jsonInt(token["maxUsesPerMember"],0ULL); + nat.push_back(t); + } + } + } + network["authTokens"] = nat; + } + } + + if (b.count("capabilities")) { + json &capabilities = b["capabilities"]; + if (capabilities.is_array()) { + std::map< uint64_t,json > ncaps; + for(unsigned long i=0;i::iterator c(ncaps.begin());c!=ncaps.end();++c) + ncapsa.push_back(c->second); + network["capabilities"] = ncapsa; + } + } + + if (b.count("tags")) { + json &tags = b["tags"]; + if (tags.is_array()) { + std::map< uint64_t,json > ntags; + for(unsigned long i=0;i::iterator t(ntags.begin());t!=ntags.end();++t) + ntagsa.push_back(t->second); + network["tags"] = ntagsa; + } + } + + } catch ( ... ) { + responseBody = "{ \"message\": \"exception occurred while parsing body variables\" }"; + responseContentType = "application/json"; + return 400; + } + + network["id"] = nwids; + network["nwid"] = nwids; // legacy + + if (network != origNetwork) { + json &revj = network["revision"]; + network["revision"] = (revj.is_number() ? ((uint64_t)revj + 1ULL) : 1ULL); + network["lastModified"] = now; + { + Mutex::Lock _l(_db_m); + _db.put("network",nwids,network); + } + + // Send an update to all members of the network + _db.filter((std::string("network/") + nwids + "/member/"),[this,&now,&nwid](const std::string &n,const json &obj) { + _pushMemberUpdate(now,nwid,obj); + return true; // do not delete + }); + } + + _NetworkMemberInfo nmi; + _getNetworkMemberInfo(now,nwid,nmi); + _addNetworkNonPersistedFields(network,now,nmi); + + responseBody = OSUtils::jsonDump(network); + responseContentType = "application/json"; + return 200; + } // else 404 + + } // else 404 + + } else if (path[0] == "ping") { + + json testRec; + const uint64_t now = OSUtils::now(); + testRec["clock"] = now; + testRec["uptime"] = (now - _startTime); + testRec["content"] = b; + responseBody = OSUtils::jsonDump(testRec); + _db.writeRaw("pong",responseBody); + responseContentType = "application/json"; + return 200; + + } + + return 404; +} + +unsigned int EmbeddedNetworkController::handleControlPlaneHttpDELETE( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType) +{ + if (path.empty()) + return 404; + + if (path[0] == "network") { + if ((path.size() >= 2)&&(path[1].length() == 16)) { + const uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); + + char nwids[24]; + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid); + json network; + { + Mutex::Lock _l(_db_m); + network = _db.get("network",nwids); + } + if (!network.size()) + return 404; + + if (path.size() >= 3) { + if ((path.size() == 4)&&(path[2] == "member")&&(path[3].length() == 10)) { + const uint64_t address = Utils::hexStrToU64(path[3].c_str()); + + Mutex::Lock _l(_db_m); + + json member = _db.get("network",nwids,"member",Address(address).toString()); + _db.erase("network",nwids,"member",Address(address).toString()); + + if (!member.size()) + return 404; + responseBody = OSUtils::jsonDump(member); + responseContentType = "application/json"; + return 200; + } + } else { + Mutex::Lock _l(_db_m); + + std::string pfx("network/"); + pfx.append(nwids); + _db.filter(pfx,[](const std::string &n,const json &obj) { + return false; // delete + }); + + Mutex::Lock _l2(_nmiCache_m); + _nmiCache.erase(nwid); + + responseBody = OSUtils::jsonDump(network); + responseContentType = "application/json"; + return 200; + } + } // else 404 + + } // else 404 + + return 404; +} + +void EmbeddedNetworkController::threadMain() + throw() +{ + uint64_t lastCircuitTestCheck = 0; + for(;;) { + _RQEntry *const qe = _queue.get(); // waits on next request + if (!qe) break; // enqueue a NULL to terminate threads + try { + _request(qe->nwid,qe->fromAddr,qe->requestPacketId,qe->identity,qe->metaData); + } catch ( ... ) {} + delete qe; + + uint64_t now = OSUtils::now(); + if ((now - lastCircuitTestCheck) > ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION) { + lastCircuitTestCheck = now; + Mutex::Lock _l(_tests_m); + for(std::list< ZT_CircuitTest >::iterator i(_tests.begin());i!=_tests.end();) { + if ((now - i->timestamp) > ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION) { + _node->circuitTestEnd(&(*i)); + _tests.erase(i++); + } else ++i; + } + } + } +} + +void EmbeddedNetworkController::_circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report) +{ + char tmp[1024],id[128]; + EmbeddedNetworkController *const self = reinterpret_cast(test->ptr); + + if ((!test)||(!report)||(!test->credentialNetworkId)) return; // sanity check + + const uint64_t now = OSUtils::now(); + Utils::snprintf(id,sizeof(id),"network/%.16llx/test/%.16llx-%.16llx-%.10llx-%.10llx",test->credentialNetworkId,test->testId,now,report->upstream,report->current); + Utils::snprintf(tmp,sizeof(tmp), + "{\"id\": \"%s\"," + "\"timestamp\": %llu," + "\"networkId\": \"%.16llx\"," + "\"testId\": \"%.16llx\"," + "\"upstream\": \"%.10llx\"," + "\"current\": \"%.10llx\"," + "\"receivedTimestamp\": %llu," + "\"sourcePacketId\": \"%.16llx\"," + "\"flags\": %llu," + "\"sourcePacketHopCount\": %u," + "\"errorCode\": %u," + "\"vendor\": %d," + "\"protocolVersion\": %u," + "\"majorVersion\": %u," + "\"minorVersion\": %u," + "\"revision\": %u," + "\"platform\": %d," + "\"architecture\": %d," + "\"receivedOnLocalAddress\": \"%s\"," + "\"receivedFromRemoteAddress\": \"%s\"," + "\"receivedFromLinkQuality\": %f}", + id + 30, // last bit only, not leading path + (unsigned long long)test->timestamp, + (unsigned long long)test->credentialNetworkId, + (unsigned long long)test->testId, + (unsigned long long)report->upstream, + (unsigned long long)report->current, + (unsigned long long)now, + (unsigned long long)report->sourcePacketId, + (unsigned long long)report->flags, + report->sourcePacketHopCount, + report->errorCode, + (int)report->vendor, + report->protocolVersion, + report->majorVersion, + report->minorVersion, + report->revision, + (int)report->platform, + (int)report->architecture, + reinterpret_cast(&(report->receivedOnLocalAddress))->toString().c_str(), + reinterpret_cast(&(report->receivedFromRemoteAddress))->toString().c_str(), + ((double)report->receivedFromLinkQuality / (double)ZT_PATH_LINK_QUALITY_MAX)); + + Mutex::Lock _l(self->_db_m); + self->_db.writeRaw(id,std::string(tmp)); +} + +void EmbeddedNetworkController::_request( + uint64_t nwid, + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary &metaData) +{ + if (((!_signingId)||(!_signingId.hasPrivate()))||(_signingId.address().toInt() != (nwid >> 24))||(!_sender)) + return; + + const uint64_t now = OSUtils::now(); + + if (requestPacketId) { + Mutex::Lock _l(_lastRequestTime_m); + uint64_t &lrt = _lastRequestTime[std::pair(identity.address().toInt(),nwid)]; + if ((now - lrt) <= ZT_NETCONF_MIN_REQUEST_PERIOD) + return; + lrt = now; + } + + char nwids[24]; + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid); + json network; + json member; + { + Mutex::Lock _l(_db_m); + network = _db.get("network",nwids); + member = _db.get("network",nwids,"member",identity.address().toString()); + } + + if (!network.size()) { + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_OBJECT_NOT_FOUND); + return; + } + + const bool newMember = (member.size() == 0); + + json origMember(member); // for detecting modification later + _initMember(member); + + { + std::string haveIdStr(OSUtils::jsonString(member["identity"],"")); + if (haveIdStr.length() > 0) { + // If we already know this member's identity perform a full compare. This prevents + // a "collision" from being able to auth onto our network in place of an already + // known member. + try { + if (Identity(haveIdStr.c_str()) != identity) { + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); + return; + } + } catch ( ... ) { + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); + return; + } + } else { + // If we do not yet know this member's identity, learn it. + member["identity"] = identity.toString(false); + } + } + + // These are always the same, but make sure they are set + member["id"] = identity.address().toString(); + member["address"] = member["id"]; + member["nwid"] = nwids; + + // Determine whether and how member is authorized + const char *authorizedBy = (const char *)0; + bool autoAuthorized = false; + json autoAuthCredentialType,autoAuthCredential; + if (OSUtils::jsonBool(member["authorized"],false)) { + authorizedBy = "memberIsAuthorized"; + } else if (!OSUtils::jsonBool(network["private"],true)) { + authorizedBy = "networkIsPublic"; + json &ahist = member["authHistory"]; + if ((!ahist.is_array())||(ahist.size() == 0)) + autoAuthorized = true; + } else { + char presentedAuth[512]; + if (metaData.get(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH,presentedAuth,sizeof(presentedAuth)) > 0) { + presentedAuth[511] = (char)0; // sanity check + + // Check for bearer token presented by member + if ((strlen(presentedAuth) > 6)&&(!strncmp(presentedAuth,"token:",6))) { + const char *const presentedToken = presentedAuth + 6; + + json &authTokens = network["authTokens"]; + if (authTokens.is_array()) { + for(unsigned long i=0;i now))&&(tstr == presentedToken)) { + bool usable = (maxUses == 0); + if (!usable) { + uint64_t useCount = 0; + json &ahist = member["authHistory"]; + if (ahist.is_array()) { + for(unsigned long j=0;j= ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH) + break; + } + } + member["recentLog"] = recentLog; + + // Also only do this on real requests + member["lastRequestMetaData"] = metaData.data(); + } + + // If they are not authorized, STOP! + if (!authorizedBy) { + if (origMember != member) { + member["lastModified"] = now; + Mutex::Lock _l(_db_m); + _db.put("network",nwids,"member",identity.address().toString(),member); + } + _sender->ncSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_ACCESS_DENIED); + return; + } + + // ------------------------------------------------------------------------- + // If we made it this far, they are authorized. + // ------------------------------------------------------------------------- + + NetworkConfig nc; + _NetworkMemberInfo nmi; + _getNetworkMemberInfo(now,nwid,nmi); + + uint64_t credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA; + if (now > nmi.mostRecentDeauthTime) { + // If we recently de-authorized a member, shrink credential TTL/max delta to + // be below the threshold required to exclude it. Cap this to a min/max to + // prevent jitter or absurdly large values. + const uint64_t deauthWindow = now - nmi.mostRecentDeauthTime; + if (deauthWindow < ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MIN_MAX_DELTA) { + credentialtmd = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MIN_MAX_DELTA; + } else if (deauthWindow < (ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA + 5000ULL)) { + credentialtmd = deauthWindow - 5000ULL; + } + } + + nc.networkId = nwid; + nc.type = OSUtils::jsonBool(network["private"],true) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; + nc.timestamp = now; + nc.credentialTimeMaxDelta = credentialtmd; + nc.revision = OSUtils::jsonInt(network["revision"],0ULL); + nc.issuedTo = identity.address(); + if (OSUtils::jsonBool(network["enableBroadcast"],true)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST; + if (OSUtils::jsonBool(network["allowPassiveBridging"],false)) nc.flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING; + Utils::scopy(nc.name,sizeof(nc.name),OSUtils::jsonString(network["name"],"").c_str()); + nc.multicastLimit = (unsigned int)OSUtils::jsonInt(network["multicastLimit"],32ULL); + + for(std::set
::const_iterator ab(nmi.activeBridges.begin());ab!=nmi.activeBridges.end();++ab) { + nc.addSpecialist(*ab,ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); + } + + json &v4AssignMode = network["v4AssignMode"]; + json &v6AssignMode = network["v6AssignMode"]; + json &ipAssignmentPools = network["ipAssignmentPools"]; + json &routes = network["routes"]; + json &rules = network["rules"]; + json &capabilities = network["capabilities"]; + json &tags = network["tags"]; + json &memberCapabilities = member["capabilities"]; + json &memberTags = member["tags"]; + + if (metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV,0) <= 0) { + // Old versions with no rules engine support get an allow everything rule. + // Since rules are enforced bidirectionally, newer versions *will* still + // enforce rules on the inbound side. + nc.ruleCount = 1; + nc.rules[0].t = ZT_NETWORK_RULE_ACTION_ACCEPT; + } else { + if (rules.is_array()) { + for(unsigned long i=0;i= ZT_MAX_NETWORK_RULES) + break; + if (_parseRule(rules[i],nc.rules[nc.ruleCount])) + ++nc.ruleCount; + } + } + + std::map< uint64_t,json * > capsById; + if (!memberCapabilities.is_array()) + memberCapabilities = json::array(); + if (capabilities.is_array()) { + for(unsigned long i=0;i::const_iterator ctmp = capsById.find(capId); + if (ctmp != capsById.end()) { + json *cap = ctmp->second; + if ((cap)&&(cap->is_object())&&(cap->size() > 0)) { + ZT_VirtualNetworkRule capr[ZT_MAX_CAPABILITY_RULES]; + unsigned int caprc = 0; + json &caprj = (*cap)["rules"]; + if ((caprj.is_array())&&(caprj.size() > 0)) { + for(unsigned long j=0;j= ZT_MAX_CAPABILITY_RULES) + break; + if (_parseRule(caprj[j],capr[caprc])) + ++caprc; + } + } + nc.capabilities[nc.capabilityCount] = Capability((uint32_t)capId,nwid,now,1,capr,caprc); + if (nc.capabilities[nc.capabilityCount].sign(_signingId,identity.address())) + ++nc.capabilityCount; + if (nc.capabilityCount >= ZT_MAX_NETWORK_CAPABILITIES) + break; + } + } + } + + std::map< uint32_t,uint32_t > memberTagsById; + if (memberTags.is_array()) { + for(unsigned long i=0;i::const_iterator t(memberTagsById.begin());t!=memberTagsById.end();++t) { + if (nc.tagCount >= ZT_MAX_NETWORK_TAGS) + break; + nc.tags[nc.tagCount] = Tag(nwid,now,identity.address(),t->first,t->second); + if (nc.tags[nc.tagCount].sign(_signingId)) + ++nc.tagCount; + } + } + + if (routes.is_array()) { + for(unsigned long i=0;i= ZT_MAX_NETWORK_ROUTES) + break; + json &route = routes[i]; + json &target = route["target"]; + json &via = route["via"]; + if (target.is_string()) { + const InetAddress t(target.get()); + InetAddress v; + if (via.is_string()) v.fromString(via.get()); + if ((t.ss_family == AF_INET)||(t.ss_family == AF_INET6)) { + ZT_VirtualNetworkRoute *r = &(nc.routes[nc.routeCount]); + *(reinterpret_cast(&(r->target))) = t; + if (v.ss_family == t.ss_family) + *(reinterpret_cast(&(r->via))) = v; + ++nc.routeCount; + } + } + } + } + + const bool noAutoAssignIps = OSUtils::jsonBool(member["noAutoAssignIps"],false); + + if ((v6AssignMode.is_object())&&(!noAutoAssignIps)) { + if ((OSUtils::jsonBool(v6AssignMode["rfc4193"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { + nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt()); + nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; + } + if ((OSUtils::jsonBool(v6AssignMode["6plane"],false))&&(nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { + nc.staticIps[nc.staticIpCount++] = InetAddress::makeIpv66plane(nwid,identity.address().toInt()); + nc.flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; + } + } + + bool haveManagedIpv4AutoAssignment = false; + bool haveManagedIpv6AutoAssignment = false; // "special" NDP-emulated address types do not count + json ipAssignments = member["ipAssignments"]; // we want to make a copy + if (ipAssignments.is_array()) { + for(unsigned long i=0;i(&(nc.routes[rk].target))->containsAddress(ip)) ) + routedNetmaskBits = reinterpret_cast(&(nc.routes[rk].target))->netmaskBits(); + } + + if (routedNetmaskBits > 0) { + if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) { + ip.setPort(routedNetmaskBits); + nc.staticIps[nc.staticIpCount++] = ip; + } + if (ip.ss_family == AF_INET) + haveManagedIpv4AutoAssignment = true; + else if (ip.ss_family == AF_INET6) + haveManagedIpv6AutoAssignment = true; + } + } + } else { + ipAssignments = json::array(); + } + + if ( (ipAssignmentPools.is_array()) && ((v6AssignMode.is_object())&&(OSUtils::jsonBool(v6AssignMode["zt"],false))) && (!haveManagedIpv6AutoAssignment) && (!noAutoAssignIps) ) { + for(unsigned long p=0;((p s[1])&&((e[1] - s[1]) >= 0xffffffffffULL)) { + // First see if we can just cram a ZeroTier ID into the higher 64 bits. If so do that. + xx[0] = Utils::hton(x[0]); + xx[1] = Utils::hton(x[1] + identity.address().toInt()); + } else { + // Otherwise pick random addresses -- this technically doesn't explore the whole range if the lower 64 bit range is >= 1 but that won't matter since that would be huge anyway + Utils::getSecureRandom((void *)xx,16); + if ((e[0] > s[0])) + xx[0] %= (e[0] - s[0]); + else xx[0] = 0; + if ((e[1] > s[1])) + xx[1] %= (e[1] - s[1]); + else xx[1] = 0; + xx[0] = Utils::hton(x[0] + xx[0]); + xx[1] = Utils::hton(x[1] + xx[1]); + } + + InetAddress ip6((const void *)xx,16,0); + + // Check if this IP is within a local-to-Ethernet routed network + int routedNetmaskBits = 0; + for(unsigned int rk=0;rk(&(nc.routes[rk].target))->containsAddress(ip6)) ) + routedNetmaskBits = reinterpret_cast(&(nc.routes[rk].target))->netmaskBits(); + } + + // If it's routed, then try to claim and assign it and if successful end loop + if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip6))) { + ipAssignments.push_back(ip6.toIpString()); + member["ipAssignments"] = ipAssignments; + ip6.setPort((unsigned int)routedNetmaskBits); + if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) + nc.staticIps[nc.staticIpCount++] = ip6; + haveManagedIpv6AutoAssignment = true; + _clearNetworkMemberInfoCache(nwid); // clear cache to prevent IP assignment duplication on many rapid assigns + break; + } + } + } + } + } + } + + if ( (ipAssignmentPools.is_array()) && ((v4AssignMode.is_object())&&(OSUtils::jsonBool(v4AssignMode["zt"],false))) && (!haveManagedIpv4AutoAssignment) && (!noAutoAssignIps) ) { + for(unsigned long p=0;((p(&ipRangeStartIA)->sin_addr.s_addr)); + uint32_t ipRangeEnd = Utils::ntoh((uint32_t)(reinterpret_cast(&ipRangeEndIA)->sin_addr.s_addr)); + if ((ipRangeEnd < ipRangeStart)||(ipRangeStart == 0)) + continue; + uint32_t ipRangeLen = ipRangeEnd - ipRangeStart; + + // Start with the LSB of the member's address + uint32_t ipTrialCounter = (uint32_t)(identity.address().toInt() & 0xffffffff); + + for(uint32_t k=ipRangeStart,trialCount=0;((k<=ipRangeEnd)&&(trialCount < 1000));++k,++trialCount) { + uint32_t ip = (ipRangeLen > 0) ? (ipRangeStart + (ipTrialCounter % ipRangeLen)) : ipRangeStart; + ++ipTrialCounter; + if ((ip & 0x000000ff) == 0x000000ff) + continue; // don't allow addresses that end in .255 + + // Check if this IP is within a local-to-Ethernet routed network + int routedNetmaskBits = -1; + for(unsigned int rk=0;rk(&(nc.routes[rk].target))->sin_addr.s_addr)); + int targetBits = Utils::ntoh((uint16_t)(reinterpret_cast(&(nc.routes[rk].target))->sin_port)); + if ((ip & (0xffffffff << (32 - targetBits))) == targetIp) { + routedNetmaskBits = targetBits; + break; + } + } + } + + // If it's routed, then try to claim and assign it and if successful end loop + const InetAddress ip4(Utils::hton(ip),0); + if ((routedNetmaskBits > 0)&&(!nmi.allocatedIps.count(ip4))) { + ipAssignments.push_back(ip4.toIpString()); + member["ipAssignments"] = ipAssignments; + if (nc.staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES) { + struct sockaddr_in *const v4ip = reinterpret_cast(&(nc.staticIps[nc.staticIpCount++])); + v4ip->sin_family = AF_INET; + v4ip->sin_port = Utils::hton((uint16_t)routedNetmaskBits); + v4ip->sin_addr.s_addr = Utils::hton(ip); + } + haveManagedIpv4AutoAssignment = true; + _clearNetworkMemberInfoCache(nwid); // clear cache to prevent IP assignment duplication on many rapid assigns + break; + } + } + } + } + } + } + + // Issue a certificate of ownership for all static IPs + if (nc.staticIpCount) { + nc.certificatesOfOwnership[0] = CertificateOfOwnership(nwid,now,identity.address(),1); + for(unsigned int i=0;incSendError(nwid,requestPacketId,identity.address(),NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR); + return; + } + + if (member != origMember) { + member["lastModified"] = now; + Mutex::Lock _l(_db_m); + _db.put("network",nwids,"member",identity.address().toString(),member); + } + + _sender->ncSendConfig(nwid,requestPacketId,identity.address(),nc,metaData.getUI(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,0) < 6); +} + +void EmbeddedNetworkController::_getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi) +{ + char pfx[256]; + Utils::snprintf(pfx,sizeof(pfx),"network/%.16llx/member",nwid); + + { + Mutex::Lock _l(_nmiCache_m); + std::map::iterator c(_nmiCache.find(nwid)); + if ((c != _nmiCache.end())&&((now - c->second.nmiTimestamp) < 1000)) { // a short duration cache but limits CPU use on big networks + nmi = c->second; + return; + } + } + + { + Mutex::Lock _l(_db_m); + _db.filter(pfx,[&nmi,&now](const std::string &n,const json &member) { + try { + if (OSUtils::jsonBool(member["authorized"],false)) { + ++nmi.authorizedMemberCount; + + if (member.count("recentLog")) { + const json &mlog = member["recentLog"]; + if ((mlog.is_array())&&(mlog.size() > 0)) { + const json &mlog1 = mlog[0]; + if (mlog1.is_object()) { + if ((now - OSUtils::jsonInt(mlog1["ts"],0ULL)) < ZT_NETCONF_NODE_ACTIVE_THRESHOLD) + ++nmi.activeMemberCount; + } + } + } + + if (OSUtils::jsonBool(member["activeBridge"],false)) { + nmi.activeBridges.insert(Address(Utils::hexStrToU64(OSUtils::jsonString(member["id"],"0000000000").c_str()))); + } + + if (member.count("ipAssignments")) { + const json &mips = member["ipAssignments"]; + if (mips.is_array()) { + for(unsigned long i=0;i 0)&&(mdstr.length() > 0)) { + const Identity id(idstr); + bool online; + { + Mutex::Lock _l(_lastRequestTime_m); + std::map< std::pair,uint64_t >::iterator lrt(_lastRequestTime.find(std::pair(id.address().toInt(),nwid))); + online = ( (lrt != _lastRequestTime.end()) && ((now - lrt->second) < ZT_NETWORK_AUTOCONF_DELAY) ); + } + if (online) { + Dictionary *metaData = new Dictionary(mdstr.c_str()); + try { + this->request(nwid,InetAddress(),0,id,*metaData); + } catch ( ... ) {} + delete metaData; + } + } + } catch ( ... ) {} +} + +} // namespace ZeroTier diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp new file mode 100644 index 0000000..0ae2f3b --- /dev/null +++ b/controller/EmbeddedNetworkController.hpp @@ -0,0 +1,210 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + */ + +#ifndef ZT_SQLITENETWORKCONTROLLER_HPP +#define ZT_SQLITENETWORKCONTROLLER_HPP + +#include + +#include +#include +#include +#include +#include + +#include "../node/Constants.hpp" + +#include "../node/NetworkController.hpp" +#include "../node/Mutex.hpp" +#include "../node/Utils.hpp" +#include "../node/Address.hpp" +#include "../node/InetAddress.hpp" + +#include "../osdep/OSUtils.hpp" +#include "../osdep/Thread.hpp" +#include "../osdep/BlockingQueue.hpp" + +#include "../ext/json/json.hpp" + +#include "JSONDB.hpp" + +// Number of background threads to start -- not actually started until needed +#define ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT 4 + +// TTL for circuit tests +#define ZT_EMBEDDEDNETWORKCONTROLLER_CIRCUIT_TEST_EXPIRATION 120000 + +namespace ZeroTier { + +class Node; + +class EmbeddedNetworkController : public NetworkController +{ +public: + /** + * @param node Parent node + * @param dbPath Path to store data + */ + EmbeddedNetworkController(Node *node,const char *dbPath); + virtual ~EmbeddedNetworkController(); + + virtual void init(const Identity &signingId,Sender *sender); + + virtual void request( + uint64_t nwid, + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary &metaData); + + unsigned int handleControlPlaneHttpGET( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType); + unsigned int handleControlPlaneHttpPOST( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType); + unsigned int handleControlPlaneHttpDELETE( + const std::vector &path, + const std::map &urlArgs, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType); + + void threadMain() + throw(); + +private: + struct _RQEntry + { + uint64_t nwid; + uint64_t requestPacketId; + InetAddress fromAddr; + Identity identity; + Dictionary metaData; + }; + + // Gathers a bunch of statistics about members of a network, IP assignments, etc. that we need in various places + struct _NetworkMemberInfo + { + _NetworkMemberInfo() : authorizedMemberCount(0),activeMemberCount(0),totalMemberCount(0),mostRecentDeauthTime(0) {} + std::set
activeBridges; + std::set allocatedIps; + unsigned long authorizedMemberCount; + unsigned long activeMemberCount; + unsigned long totalMemberCount; + uint64_t mostRecentDeauthTime; + uint64_t nmiTimestamp; // time this NMI structure was computed + }; + + static void _circuitTestCallback(ZT_Node *node,ZT_CircuitTest *test,const ZT_CircuitTestReport *report); + void _request(uint64_t nwid,const InetAddress &fromAddr,uint64_t requestPacketId,const Identity &identity,const Dictionary &metaData); + void _getNetworkMemberInfo(uint64_t now,uint64_t nwid,_NetworkMemberInfo &nmi); + inline void _clearNetworkMemberInfoCache(const uint64_t nwid) { Mutex::Lock _l(_nmiCache_m); _nmiCache.erase(nwid); } + void _pushMemberUpdate(uint64_t now,uint64_t nwid,const nlohmann::json &member); + + // These init objects with default and static/informational fields + inline void _initMember(nlohmann::json &member) + { + if (!member.count("authorized")) member["authorized"] = false; + if (!member.count("authHistory")) member["authHistory"] = nlohmann::json::array(); + if (!member.count("ipAssignments")) member["ipAssignments"] = nlohmann::json::array(); + if (!member.count("recentLog")) member["recentLog"] = nlohmann::json::array(); + if (!member.count("activeBridge")) member["activeBridge"] = false; + if (!member.count("tags")) member["tags"] = nlohmann::json::array(); + if (!member.count("capabilities")) member["capabilities"] = nlohmann::json::array(); + if (!member.count("creationTime")) member["creationTime"] = OSUtils::now(); + if (!member.count("noAutoAssignIps")) member["noAutoAssignIps"] = false; + if (!member.count("revision")) member["revision"] = 0ULL; + if (!member.count("lastDeauthorizedTime")) member["lastDeauthorizedTime"] = 0ULL; + if (!member.count("lastAuthorizedTime")) member["lastAuthorizedTime"] = 0ULL; + member["objtype"] = "member"; + } + inline void _initNetwork(nlohmann::json &network) + { + if (!network.count("private")) network["private"] = true; + if (!network.count("creationTime")) network["creationTime"] = OSUtils::now(); + if (!network.count("name")) network["name"] = ""; + if (!network.count("multicastLimit")) network["multicastLimit"] = (uint64_t)32; + if (!network.count("enableBroadcast")) network["enableBroadcast"] = true; + if (!network.count("v4AssignMode")) network["v4AssignMode"] = {{"zt",false}}; + if (!network.count("v6AssignMode")) network["v6AssignMode"] = {{"rfc4193",false},{"zt",false},{"6plane",false}}; + if (!network.count("authTokens")) network["authTokens"] = nlohmann::json::array(); + if (!network.count("capabilities")) network["capabilities"] = nlohmann::json::array(); + if (!network.count("tags")) network["tags"] = nlohmann::json::array(); + if (!network.count("routes")) network["routes"] = nlohmann::json::array(); + if (!network.count("ipAssignmentPools")) network["ipAssignmentPools"] = nlohmann::json::array(); + if (!network.count("rules")) { + // If unspecified, rules are set to allow anything and behave like a flat L2 segment + network["rules"] = {{ + { "not",false }, + { "or", false }, + { "type","ACTION_ACCEPT" } + }}; + } + network["objtype"] = "network"; + } + inline void _addNetworkNonPersistedFields(nlohmann::json &network,uint64_t now,const _NetworkMemberInfo &nmi) + { + network["clock"] = now; + network["authorizedMemberCount"] = nmi.authorizedMemberCount; + network["activeMemberCount"] = nmi.activeMemberCount; + network["totalMemberCount"] = nmi.totalMemberCount; + } + inline void _addMemberNonPersistedFields(nlohmann::json &member,uint64_t now) + { + member["clock"] = now; + } + + const uint64_t _startTime; + + BlockingQueue<_RQEntry *> _queue; + Thread _threads[ZT_EMBEDDEDNETWORKCONTROLLER_BACKGROUND_THREAD_COUNT]; + bool _threadsStarted; + Mutex _threads_m; + + std::map _nmiCache; + Mutex _nmiCache_m; + + JSONDB _db; + Mutex _db_m; + + Node *const _node; + std::string _path; + + NetworkController::Sender *_sender; + Identity _signingId; + + std::list< ZT_CircuitTest > _tests; + Mutex _tests_m; + + std::map< std::pair,uint64_t > _lastRequestTime; // last request time by + Mutex _lastRequestTime_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/controller/JSONDB.cpp b/controller/JSONDB.cpp new file mode 100644 index 0000000..d3e76fc --- /dev/null +++ b/controller/JSONDB.cpp @@ -0,0 +1,219 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + */ + +#include "JSONDB.hpp" + +#define ZT_JSONDB_HTTP_TIMEOUT 60000 + +namespace ZeroTier { + +static const nlohmann::json _EMPTY_JSON(nlohmann::json::object()); +static const std::map _ZT_JSONDB_GET_HEADERS; + +JSONDB::JSONDB(const std::string &basePath) : + _basePath(basePath), + _ready(false) +{ + if ((_basePath.length() > 7)&&(_basePath.substr(0,7) == "http://")) { + // TODO: this doesn't yet support IPv6 since bracketed address notiation isn't supported. + // Typically it's used with 127.0.0.1 anyway. + std::string hn = _basePath.substr(7); + std::size_t hnend = hn.find_first_of('/'); + if (hnend != std::string::npos) + hn = hn.substr(0,hnend); + std::size_t hnsep = hn.find_last_of(':'); + if (hnsep != std::string::npos) + hn[hnsep] = '/'; + _httpAddr.fromString(hn); + if (hnend != std::string::npos) + _basePath = _basePath.substr(7 + hnend); + if (_basePath.length() == 0) + _basePath = "/"; + if (_basePath[0] != '/') + _basePath = std::string("/") + _basePath; + } else { + OSUtils::mkdir(_basePath.c_str()); + OSUtils::lockDownFile(_basePath.c_str(),true); // networks might contain auth tokens, etc., so restrict directory permissions + } + _ready = _reload(_basePath,std::string()); +} + +bool JSONDB::writeRaw(const std::string &n,const std::string &obj) +{ + if (!_isValidObjectName(n)) + return false; + if (_httpAddr) { + std::map headers; + std::string body; + std::map reqHeaders; + char tmp[64]; + Utils::snprintf(tmp,sizeof(tmp),"%lu",(unsigned long)obj.length()); + reqHeaders["Content-Length"] = tmp; + reqHeaders["Content-Type"] = "application/json"; + const unsigned int sc = Http::PUT(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast(&_httpAddr),(_basePath+"/"+n).c_str(),reqHeaders,obj.data(),(unsigned long)obj.length(),headers,body); + return (sc == 200); + } else { + const std::string path(_genPath(n,true)); + if (!path.length()) + return false; + return OSUtils::writeFile(path.c_str(),obj); + } +} + +bool JSONDB::put(const std::string &n,const nlohmann::json &obj) +{ + const bool r = writeRaw(n,OSUtils::jsonDump(obj)); + _db[n].obj = obj; + return r; +} + +const nlohmann::json &JSONDB::get(const std::string &n) +{ + while (!_ready) { + Thread::sleep(250); + _ready = _reload(_basePath,std::string()); + } + + if (!_isValidObjectName(n)) + return _EMPTY_JSON; + std::map::iterator e(_db.find(n)); + if (e != _db.end()) + return e->second.obj; + + std::string buf; + if (_httpAddr) { + std::map headers; + const unsigned int sc = Http::GET(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast(&_httpAddr),(_basePath+"/"+n).c_str(),_ZT_JSONDB_GET_HEADERS,headers,buf); + if (sc != 200) + return _EMPTY_JSON; + } else { + const std::string path(_genPath(n,false)); + if (!path.length()) + return _EMPTY_JSON; + if (!OSUtils::readFile(path.c_str(),buf)) + return _EMPTY_JSON; + } + + try { + _E &e2 = _db[n]; + e2.obj = OSUtils::jsonParse(buf); + return e2.obj; + } catch ( ... ) { + _db.erase(n); + return _EMPTY_JSON; + } +} + +void JSONDB::erase(const std::string &n) +{ + if (!_isValidObjectName(n)) + return; + + if (_httpAddr) { + std::string body; + std::map headers; + Http::DEL(1048576,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast(&_httpAddr),(_basePath+"/"+n).c_str(),_ZT_JSONDB_GET_HEADERS,headers,body); + } else { + std::string path(_genPath(n,true)); + if (!path.length()) + return; + OSUtils::rm(path.c_str()); + } + + _db.erase(n); +} + +bool JSONDB::_reload(const std::string &p,const std::string &b) +{ + if (_httpAddr) { + std::string body; + std::map headers; + const unsigned int sc = Http::GET(2147483647,ZT_JSONDB_HTTP_TIMEOUT,reinterpret_cast(&_httpAddr),_basePath.c_str(),_ZT_JSONDB_GET_HEADERS,headers,body); + if (sc == 200) { + try { + nlohmann::json dbImg(OSUtils::jsonParse(body)); + std::string tmp; + if (dbImg.is_object()) { + for(nlohmann::json::iterator i(dbImg.begin());i!=dbImg.end();++i) { + if (i.value().is_object()) { + tmp = i.key(); + _db[tmp].obj = i.value(); + } + } + return true; + } + } catch ( ... ) {} // invalid JSON, so maybe incomplete request + } + return false; + } else { + std::vector dl(OSUtils::listDirectory(p.c_str(),true)); + for(std::vector::const_iterator di(dl.begin());di!=dl.end();++di) { + if ((di->length() > 5)&&(di->substr(di->length() - 5) == ".json")) { + this->get(b + di->substr(0,di->length() - 5)); + } else { + this->_reload((p + ZT_PATH_SEPARATOR + *di),(b + *di + ZT_PATH_SEPARATOR)); + } + } + return true; + } +} + +bool JSONDB::_isValidObjectName(const std::string &n) +{ + if (n.length() == 0) + return false; + const char *p = n.c_str(); + char c; + // For security reasons we should not allow dots, backslashes, or other path characters or potential path characters. + while ((c = *(p++))) { + if (!( ((c >= 'a')&&(c <= 'z')) || ((c >= 'A')&&(c <= 'Z')) || ((c >= '0')&&(c <= '9')) || (c == '/') || (c == '_') || (c == '~') || (c == '-') )) + return false; + } + return true; +} + +std::string JSONDB::_genPath(const std::string &n,bool create) +{ + std::vector pt(OSUtils::split(n.c_str(),"/","","")); + if (pt.size() == 0) + return std::string(); + + char sep; + if (_httpAddr) { + sep = '/'; + create = false; + } else { + sep = ZT_PATH_SEPARATOR; + } + + std::string p(_basePath); + if (create) OSUtils::mkdir(p.c_str()); + for(unsigned long i=0,j=(unsigned long)(pt.size()-1);i. + */ + +#ifndef ZT_JSONDB_HPP +#define ZT_JSONDB_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" +#include "../node/InetAddress.hpp" +#include "../node/Mutex.hpp" +#include "../ext/json/json.hpp" +#include "../osdep/OSUtils.hpp" +#include "../osdep/Http.hpp" +#include "../osdep/Thread.hpp" + +namespace ZeroTier { + +/** + * Hierarchical JSON store that persists into the filesystem or via HTTP + */ +class JSONDB +{ +public: + JSONDB(const std::string &basePath); + + bool writeRaw(const std::string &n,const std::string &obj); + + bool put(const std::string &n,const nlohmann::json &obj); + + inline bool put(const std::string &n1,const std::string &n2,const nlohmann::json &obj) { return this->put((n1 + "/" + n2),obj); } + inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3),obj); } + inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3 + "/" + n4),obj); } + inline bool put(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5,const nlohmann::json &obj) { return this->put((n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5),obj); } + + const nlohmann::json &get(const std::string &n); + + inline const nlohmann::json &get(const std::string &n1,const std::string &n2) { return this->get((n1 + "/" + n2)); } + inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3) { return this->get((n1 + "/" + n2 + "/" + n3)); } + inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4) { return this->get((n1 + "/" + n2 + "/" + n3 + "/" + n4)); } + inline const nlohmann::json &get(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5) { return this->get((n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5)); } + + void erase(const std::string &n); + + inline void erase(const std::string &n1,const std::string &n2) { this->erase(n1 + "/" + n2); } + inline void erase(const std::string &n1,const std::string &n2,const std::string &n3) { this->erase(n1 + "/" + n2 + "/" + n3); } + inline void erase(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4) { this->erase(n1 + "/" + n2 + "/" + n3 + "/" + n4); } + inline void erase(const std::string &n1,const std::string &n2,const std::string &n3,const std::string &n4,const std::string &n5) { this->erase(n1 + "/" + n2 + "/" + n3 + "/" + n4 + "/" + n5); } + + template + inline void filter(const std::string &prefix,F func) + { + while (!_ready) { + Thread::sleep(250); + _ready = _reload(_basePath,std::string()); + } + + for(std::map::iterator i(_db.lower_bound(prefix));i!=_db.end();) { + if ((i->first.length() >= prefix.length())&&(!memcmp(i->first.data(),prefix.data(),prefix.length()))) { + if (!func(i->first,get(i->first))) { + std::map::iterator i2(i); ++i2; + this->erase(i->first); + i = i2; + } else ++i; + } else break; + } + } + + inline bool operator==(const JSONDB &db) const { return ((_basePath == db._basePath)&&(_db == db._db)); } + inline bool operator!=(const JSONDB &db) const { return (!(*this == db)); } + +private: + bool _reload(const std::string &p,const std::string &b); + bool _isValidObjectName(const std::string &n); + std::string _genPath(const std::string &n,bool create); + + struct _E + { + nlohmann::json obj; + inline bool operator==(const _E &e) const { return (obj == e.obj); } + inline bool operator!=(const _E &e) const { return (obj != e.obj); } + }; + + InetAddress _httpAddr; + std::string _basePath; + std::map _db; + volatile bool _ready; +}; + +} // namespace ZeroTier + +#endif diff --git a/controller/README.md b/controller/README.md new file mode 100644 index 0000000..db8d015 --- /dev/null +++ b/controller/README.md @@ -0,0 +1,248 @@ +Network Controller Microservice +====== + +Every ZeroTier virtual network has a *network controller*. This is our reference implementation and is the same one we use to power our own hosted services at [my.zerotier.com](https://my.zerotier.com/). Network controllers act as configuration servers and certificate authorities for the members of networks. Controllers are located on the network by simply parsing out the first 10 digits of a network's 16-digit network ID: these are the address of the controller. + +As of ZeroTier One version 1.2.0 this code is included in normal builds for desktop, laptop, and server (Linux, etc.) targets, allowing any device to create virtual networks without having to be rebuilt from source with special flags to enable this feature. While this does offer a convenient way to create ad-hoc networks or experiment, we recommend running a dedicated controller somewhere secure and stable for any "serious" use case. + +Controller data is stored in JSON format under `controller.d` in the ZeroTier working directory. It can be copied, rsync'd, placed in `git`, etc. The files under `controller.d` should not be modified in place while the controller is running or data loss may result, and if they are edited directly take care not to save corrupt JSON since that can also lead to data loss when the controller is restarted. Going through the API is strongly preferred to directly modifying these files. + +### Upgrading from Older (1.1.14 or earlier) Versions + +Older versions of this code used a SQLite database instead of in-filesystem JSON. A migration utility called `migrate-sqlite` is included here and *must* be used to migrate this data to the new format. If the controller is started with an old `controller.db` in its working directory it will terminate after printing an error to *stderr*. This is done to prevent "surprises" for those running DIY controllers using the old code. + +The migration tool is written in nodeJS and can be used like this: + + cd migrate-sqlite + npm install + node migrate.js + +Very old versions of nodeJS may have issues. We tested it with version 7. + +### Scalability and Reliability + +Controllers can in theory host up to 2^24 networks and serve many millions of devices (or more), but we recommend spreading large numbers of networks across many controllers for load balancing and fault tolerance reasons. Since the controller uses the filesystem as its data store we recommend fast filesystems and fast SSD drives for heavily loaded controllers. + +Since ZeroTier nodes are mobile and do not need static IPs, implementing high availability fail-over for controllers is easy. Just replicate their working directories from master to backup and have something automatically fire up the backup if the master goes down. Many modern orchestration tools have built-in support for this. It would also be possible in theory to run controllers on a replicated or distributed filesystem, but we haven't tested this yet. + +### Dockerizing Controllers + +ZeroTier network controllers can easily be run in Docker or other container systems. Since containers do not need to actually join networks, extra privilege options like "--device=/dev/net/tun --privileged" are not needed. You'll just need to map the local JSON API port of the running controller and allow it to access the Internet (over UDP/9993 at a minimum) so things can reach and query it. + +### Network Controller API + +The controller API is hosted via the same JSON API endpoint that ZeroTier One uses for local control (usually at 127.0.0.1 port 9993). All controller options are routed under the `/controller` base path. + +The controller microservice does not implement any fine-grained access control (authentication is via authtoken.secret just like the regular JSON API) or other complex mangement features. It just takes network and network member configurations and reponds to controller queries. We have an enterprise product called [ZeroTier Central](https://my.zerotier.com/) that we host as a service (and that companies can license to self-host) that does this. + +All working network IDs on a controller must begin with the controller's ZeroTier address. The API will *allow* "foreign" networks to be added but the controller will have no way of doing anything with them since nobody will know to query it. (In the future we might support secondaries, which would make this relevant.) + +The JSON API is *very* sensitive about types. Integers must be integers and strings strings, etc. Incorrectly typed and unrecognized fields may result in ignored fields or a 400 (bad request) error. + +#### `/controller` + + * Purpose: Check for controller function and return controller status + * Methods: GET + * Returns: { object } + +| Field | Type | Description | Writable | +| ------------------ | ----------- | ------------------------------------------------- | -------- | +| controller | boolean | Always 'true' | no | +| apiVersion | integer | Controller API version, currently 3 | no | +| clock | integer | Current clock on controller, ms since epoch | no | + +#### `/controller/network` + + * Purpose: List all networks hosted by this controller + * Methods: GET + * Returns: [ string, ... ] + +This returns an array of 16-digit hexadecimal network IDs. + +#### `/controller/network/` + + * Purpose: Create, configure, and delete hosted networks + * Methods: GET, POST, DELETE + * Returns: { object } + +By making queries to this path you can create, configure, and delete networks. DELETE is final, so don't do it unless you really mean it. + +When POSTing new networks take care that their IDs are not in use, otherwise you may overwrite an existing one. To create a new network with a random unused ID, POST to `/controller/network/##########______`. The #'s are the controller's 10-digit ZeroTier address and they're followed by six underscores. Check the `nwid` field of the returned JSON object for your network's newly allocated ID. Subsequent POSTs to this network must refer to its actual path. + +| Field | Type | Description | Writable | +| --------------------- | ------------- | ------------------------------------------------- | -------- | +| id | string | 16-digit network ID | no | +| nwid | string | 16-digit network ID (old, but still around) | no | +| clock | integer | Current clock, ms since epoch | no | +| name | string | A short name for this network | YES | +| private | boolean | Is access control enabled? | YES | +| enableBroadcast | boolean | Ethernet ff:ff:ff:ff:ff:ff allowed? | YES | +| allowPassiveBridging | boolean | Allow any member to bridge (very experimental) | YES | +| v4AssignMode | object | IPv4 management and assign options (see below) | YES | +| v6AssignMode | object | IPv6 management and assign options (see below) | YES | +| multicastLimit | integer | Maximum recipients for a multicast packet | YES | +| creationTime | integer | Time network was first created | no | +| revision | integer | Network config revision counter | no | +| authorizedMemberCount | integer | Number of authorized members (for private nets) | no | +| activeMemberCount | integer | Number of members that appear to be online | no | +| totalMemberCount | integer | Total known members of this network | no | +| routes | array[object] | Managed IPv4 and IPv6 routes; see below | YES | +| ipAssignmentPools | array[object] | IP auto-assign ranges; see below | YES | +| rules | array[object] | Traffic rules; see below | YES | + +Recent changes: + + * The `ipLocalRoutes` field appeared in older versions but is no longer present. Routes will now show up in `routes`. + * The `relays` field is gone since network preferred relays are gone. This capability is replaced by VL1 level federation ("federated roots"). + +Other important points: + + * Networks without rules won't carry any traffic. If you don't specify any on network creation an "accept anything" rule set will automatically be added. + * Managed IP address assignments and IP assignment pools that do not fall within a route configured in `routes` are ignored and won't be used or sent to members. + * The default for `private` is `true` and this is probably what you want. Turning `private` off means *anyone* can join your network with only its 16-digit network ID. It's also impossible to de-authorize a member as these networks don't issue or enforce certificates. Such "party line" networks are used for decentralized app backplanes, gaming, and testing but are otherwise not common. + +**Auto-Assign Modes:** + +Auto assign modes (`v4AssignMode` and `v6AssignMode`) contain objects that map assignment modes to booleans. + +For IPv4 the only valid setting is `zt` which, if true, causes IPv4 addresses to be auto-assigned from `ipAssignmentPools` to members that do not have an IPv4 assignment. Note that active bridges are exempt and will not get auto-assigned IPs since this can interfere with bridging. (You can still manually assign one if you want.) + +IPv6 includes this option and two others: `6plane` and `rfc4193`. These assign private IPv6 addresses to each member based on a deterministic assignment scheme that allows members to emulate IPv6 NDP to skip multicast for better performance and scalability. The `rfc4193` mode gives every member a /128 on a /88 network, while `6plane` gives every member a /80 within a /40 network but uses NDP emulation to route *all* IPs under that /80 to its owner. The `6plane` mode is great for use cases like Docker since it allows every member to assign IPv6 addresses within its /80 that just work instantly and globally across the network. + +**IP assignment pool object format:** + +| Field | Type | Description | +| --------------------- | ------------- | ------------------------------------------------- | +| ipRangeStart | string | Starting IP address in range | +| ipRangeEnd | string | Ending IP address in range (inclusive) | + +Pools are only used if auto-assignment is on for the given address type (IPv4 or IPv6) and if the entire range falls within a managed route. + +IPv6 ranges work just like IPv4 ranges and look like this: + + { + "ipRangeStart": "fd00:feed:feed:beef:0000:0000:0000:0000", + "ipRangeEnd": "fd00:feed:feed:beef:ffff:ffff:ffff:ffff" + } + +(You can POST a shortened-form IPv6 address but the API will always report back un-shortened canonical form addresses.) + +That defines a range within network `fd00:feed:feed:beef::/64` that contains up to 2^64 addresses. If an IPv6 range is large enough, the controller will assign addresses by placing each member's device ID into the address in a manner similar to the RFC4193 and 6PLANE modes. Otherwise it will assign addresses at random. + +**Rule object format:** + +Each rule is actually a sequence of zero or more `MATCH_` entries in the rule array followed by an `ACTION_` entry that describes what to do if all the preceding entries match. An `ACTION_` without any preceding `MATCH_` entries is always taken, so setting a single `ACTION_ACCEPT` rule yields a network that allows all traffic. If no rules are present the default action is `ACTION_DROP`. + +Rules are evaluated in the order in which they appear in the array. There is currently a limit of 256 entries per network. Capabilities should be used if a larger and more complex rule set is needed since they allow rules to be grouped by purpose and only shipped to members that need them. + +Each rule table entry has two common fields. + +| Field | Type | Description | +| --------------------- | ------------- | ------------------------------------------------- | +| type | string | Entry type (all caps, case sensitive) | +| not | boolean | If true, MATCHes match if they don't match | + +The following fields may or may not be present depending on rule type: + +| Field | Type | Description | +| --------------------- | ------------- | ------------------------------------------------- | +| zt | string | 10-digit hex ZeroTier address | +| etherType | integer | Ethernet frame type | +| mac | string | Hex MAC address (with or without :'s) | +| ip | string | IPv4 or IPv6 address | +| ipTos | integer | IP type of service | +| ipProtocol | integer | IP protocol (e.g. TCP) | +| start | integer | Start of an integer range (e.g. port range) | +| end | integer | End of an integer range (inclusive) | +| id | integer | Tag ID | +| value | integer | Tag value or comparison value | +| mask | integer | Bit mask (for characteristics flags) | + +The entry types and their additional fields are: + +| Entry type | Description | Fields | +| ------------------------------- | ----------------------------------------------------------------- | -------------- | +| `ACTION_DROP` | Drop any packets matching this rule | (none) | +| `ACTION_ACCEPT` | Accept any packets matching this rule | (none) | +| `ACTION_TEE` | Send a copy of this packet to a node (rule parsing continues) | `zt` | +| `ACTION_REDIRECT` | Redirect this packet to another node | `zt` | +| `ACTION_DEBUG_LOG` | Output debug info on match (if built with rules engine debug) | (none) | +| `MATCH_SOURCE_ZEROTIER_ADDRESS` | Match VL1 ZeroTier address of packet sender. | `zt` | +| `MATCH_DEST_ZEROTIER_ADDRESS` | Match VL1 ZeroTier address of recipient | `zt` | +| `MATCH_ETHERTYPE` | Match Ethernet frame type | `etherType` | +| `MATCH_MAC_SOURCE` | Match source Ethernet MAC address | `mac` | +| `MATCH_MAC_DEST` | Match destination Ethernet MAC address | `mac` | +| `MATCH_IPV4_SOURCE` | Match source IPv4 address | `ip` | +| `MATCH_IPV4_DEST` | Match destination IPv4 address | `ip` | +| `MATCH_IPV6_SOURCE` | Match source IPv6 address | `ip` | +| `MATCH_IPV6_DEST` | Match destination IPv6 address | `ip` | +| `MATCH_IP_TOS` | Match IP TOS field | `ipTos` | +| `MATCH_IP_PROTOCOL` | Match IP protocol field | `ipProtocol` | +| `MATCH_IP_SOURCE_PORT_RANGE` | Match a source IP port range | `start`,`end` | +| `MATCH_IP_DEST_PORT_RANGE` | Match a destination IP port range | `start`,`end` | +| `MATCH_CHARACTERISTICS` | Match on characteristics flags | `mask`,`value` | +| `MATCH_FRAME_SIZE_RANGE` | Match a range of Ethernet frame sizes | `start`,`end` | +| `MATCH_TAGS_SAMENESS` | Match if both sides' tags differ by no more than value | `id`,`value` | +| `MATCH_TAGS_BITWISE_AND` | Match if both sides' tags AND to value | `id`,`value` | +| `MATCH_TAGS_BITWISE_OR` | Match if both sides' tags OR to value | `id`,`value` | +| `MATCH_TAGS_BITWISE_XOR` | Match if both sides` tags XOR to value | `id`,`value` | + +Important notes about rules engine behavior: + + * IPv4 and IPv6 IP address rules do not match for frames that are not IPv4 or IPv6 respectively. + * `ACTION_DEBUG_LOG` is a no-op on nodes not built with `ZT_RULES_ENGINE_DEBUGGING` enabled (see Network.cpp). If that is enabled nodes will dump a trace of rule evaluation results to *stdout* when this action is encountered but will otherwise keep evaluating rules. This is used for basic "smoke testing" of the rules engine. + * Multicast packets and packets destined for bridged devices treated a little differently. They are matched more than once. They are matched at the point of send with a NULL ZeroTier destination address, meaning that `MATCH_DEST_ZEROTIER_ADDRESS` is useless. That's because the true VL1 destination is not yet known. Then they are matched again for each true VL1 destination. On these later subsequent matches TEE actions are ignored and REDIRECT rules are interpreted as DROPs. This prevents multiple TEE or REDIRECT packets from being sent to third party devices. + * Rules in capabilities are always matched as if the current device is the sender (inbound == false). A capability specifies sender side rules that can be enforced on both sides. + +#### `/controller/network//member` + + * Purpose: Get a set of all members on this network + * Methods: GET + * Returns: { object } + +This returns a JSON object containing all member IDs as keys and their `memberRevisionCounter` values as values. + +#### `/controller/network//active` + + * Purpose: Get a set of all active members on this network + * Methods: GET + * Returns: { object } + +This returns an object containing all currently online members and the most recent `recentLog` entries for their last request. + +#### `/controller/network//member/
` + + * Purpose: Create, authorize, or remove a network member + * Methods: GET, POST, DELETE + * Returns: { object } + +| Field | Type | Description | Writable | +| --------------------- | ------------- | ------------------------------------------------- | -------- | +| id | string | Member's 10-digit ZeroTier address | no | +| address | string | Member's 10-digit ZeroTier address | no | +| nwid | string | 16-digit network ID | no | +| clock | integer | Current clock, ms since epoch | no | +| authorized | boolean | Is member authorized? (for private networks) | YES | +| authHistory | array[object] | History of auth changes, latest at end | no | +| activeBridge | boolean | Member is able to bridge to other Ethernet nets | YES | +| identity | string | Member's public ZeroTier identity (if known) | no | +| ipAssignments | array[string] | Managed IP address assignments | YES | +| memberRevision | integer | Member revision counter | no | +| recentLog | array[object] | Recent member activity log; see below | no | + +Note that managed IP assignments are only used if they fall within a managed route. Otherwise they are ignored. + +**Recent log object format:** + +| Field | Type | Description | +| --------------------- | ------------- | ------------------------------------------------- | +| ts | integer | Time of request, ms since epoch | +| auth | boolean | Was member authorized? | +| authBy | string | How was member authorized? | +| vMajor | integer | Client major version or -1 if unknown | +| vMinor | integer | Client minor version or -1 if unknown | +| vRev | integer | Client revision or -1 if unknown | +| vProto | integer | ZeroTier protocol version reported by client | +| fromAddr | string | Physical address if known | + +The controller can only know a member's `fromAddr` if it's able to establish a direct path to it. Members behind very restrictive firewalls may not have this information since the controller will be receiving the member's requests by way of a relay. ZeroTier does not back-trace IP paths as packets are relayed since this would add a lot of protocol overhead. diff --git a/controller/migrate-sqlite/migrate.js b/controller/migrate-sqlite/migrate.js new file mode 100644 index 0000000..ac9678a --- /dev/null +++ b/controller/migrate-sqlite/migrate.js @@ -0,0 +1,320 @@ +'use strict'; + +var sqlite3 = require('sqlite3').verbose(); +var fs = require('fs'); +var async = require('async'); + +function blobToIPv4(b) +{ + if (!b) + return null; + if (b.length !== 16) + return null; + return b.readUInt8(12).toString()+'.'+b.readUInt8(13).toString()+'.'+b.readUInt8(14).toString()+'.'+b.readUInt8(15).toString(); +} +function blobToIPv6(b) +{ + if (!b) + return null; + if (b.length !== 16) + return null; + var s = ''; + for(var i=0;i<16;++i) { + var x = b.readUInt8(i).toString(16); + if (x.length === 1) + s += '0'; + s += x; + if ((((i+1) & 1) === 0)&&(i !== 15)) + s += ':'; + } + return s; +} + +if (process.argv.length !== 4) { + console.log('ZeroTier Old Sqlite3 Controller DB Migration Utility'); + console.log('(c)2017 ZeroTier, Inc. [GPL3]'); + console.log(''); + console.log('Usage: node migrate.js '); + console.log(''); + console.log('The first argument must be the path to the old Sqlite3 controller.db'); + console.log('file. The second must be the path to the EMPTY controller.d database'); + console.log('directory for a new (1.1.17 or newer) controller. If this path does'); + console.log('not exist it will be created.'); + console.log(''); + console.log('WARNING: this will ONLY work correctly on a 1.1.14 controller database.'); + console.log('If your controller is old you should first upgrade to 1.1.14 and run the'); + console.log('controller so that it will brings its Sqlite3 database up to the latest'); + console.log('version before running this migration.'); + console.log(''); + process.exit(1); +} + +var oldDbPath = process.argv[2]; +var newDbPath = process.argv[3]; + +console.log('Starting migrate of "'+oldDbPath+'" to "'+newDbPath+'"...'); +console.log(''); + +var old = new sqlite3.Database(oldDbPath); + +var networks = {}; + +var nodeIdentities = {}; +var networkCount = 0; +var memberCount = 0; +var routeCount = 0; +var ipAssignmentPoolCount = 0; +var ipAssignmentCount = 0; +var ruleCount = 0; +var oldSchemaVersion = -1; + +async.series([function(nextStep) { + + old.each('SELECT v from Config WHERE k = \'schemaVersion\'',function(err,row) { + oldSchemaVersion = parseInt(row.v)||-1; + },nextStep); + +},function(nextStep) { + + if (oldSchemaVersion !== 4) { + console.log('FATAL: this MUST be run on a 1.1.14 controller.db! Upgrade your old'); + console.log('controller to 1.1.14 first and run it once to bring its DB up to date.'); + return process.exit(1); + } + + console.log('Reading networks...'); + old.each('SELECT * FROM Network',function(err,row) { + if ((typeof row.id === 'string')&&(row.id.length === 16)) { + var flags = parseInt(row.flags)||0; + networks[row.id] = { + id: row.id, + nwid: row.id, + objtype: 'network', + authTokens: [], + capabilities: [], + creationTime: parseInt(row.creationTime)||0, + enableBroadcast: !!row.enableBroadcast, + ipAssignmentPools: [], + lastModified: Date.now(), + multicastLimit: row.multicastLimit||32, + name: row.name||'', + private: !!row.private, + revision: parseInt(row.revision)||1, + rules: [{ 'type': 'ACTION_ACCEPT' }], // populated later if there are defined rules, otherwise default is allow all + routes: [], + v4AssignMode: { + 'zt': ((flags & 1) !== 0) + }, + v6AssignMode: { + '6plane': ((flags & 4) !== 0), + 'rfc4193': ((flags & 2) !== 0), + 'zt': ((flags & 8) !== 0) + }, + _members: {} // temporary + }; + ++networkCount; + //console.log(networks[row.id]); + } + },nextStep); + +},function(nextStep) { + + console.log(' '+networkCount+' networks.'); + console.log('Reading network route definitions...'); + old.each('SELECT * from Route WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) { + var network = networks[row.networkId]; + if (network) { + var rt = { + target: (((row.ipVersion == 4) ? blobToIPv4(row.target) : blobToIPv6(row.target))+'/'+row.targetNetmaskBits), + via: ((row.via) ? ((row.ipVersion == 4) ? blobToIPv4(row.via) : blobToIPv6(row.via)) : null) + }; + network.routes.push(rt); + ++routeCount; + } + },nextStep); + +},function(nextStep) { + + console.log(' '+routeCount+' routes in '+networkCount+' networks.'); + console.log('Reading IP assignment pools...'); + old.each('SELECT * FROM IpAssignmentPool WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) { + var network = networks[row.networkId]; + if (network) { + var p = { + ipRangeStart: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeStart) : blobToIPv6(row.ipRangeStart)), + ipRangeEnd: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeEnd) : blobToIPv6(row.ipRangeEnd)) + }; + network.ipAssignmentPools.push(p); + ++ipAssignmentPoolCount; + } + },nextStep); + +},function(nextStep) { + + console.log(' '+ipAssignmentPoolCount+' IP assignment pools in '+networkCount+' networks.'); + console.log('Reading known node identities...'); + old.each('SELECT * FROM Node',function(err,row) { + nodeIdentities[row.id] = row.identity; + },nextStep); + +},function(nextStep) { + + console.log(' '+Object.keys(nodeIdentities).length+' known identities.'); + console.log('Reading network members...'); + old.each('SELECT * FROM Member',function(err,row) { + var network = networks[row.networkId]; + if (network) { + network._members[row.nodeId] = { + id: row.nodeId, + address: row.nodeId, + objtype: 'member', + authorized: !!row.authorized, + activeBridge: !!row.activeBridge, + authHistory: [], + capabilities: [], + creationTime: 0, + identity: nodeIdentities[row.nodeId]||null, + ipAssignments: [], + lastAuthorizedTime: (row.authorized) ? Date.now() : 0, + lastDeauthorizedTime: (row.authorized) ? 0 : Date.now(), + lastModified: Date.now(), + lastRequestMetaData: '', + noAutoAssignIps: false, + nwid: row.networkId, + revision: parseInt(row.memberRevision)||1, + tags: [], + recentLog: [] + }; + ++memberCount; + //console.log(network._members[row.nodeId]); + } + },nextStep); + +},function(nextStep) { + + console.log(' '+memberCount+' members of '+networkCount+' networks.'); + console.log('Reading static IP assignments...'); + old.each('SELECT * FROM IpAssignment WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) { + var network = networks[row.networkId]; + if (network) { + var member = network._members[row.nodeId]; + if ((member)&&((member.authorized)||(!network['private']))) { // don't mirror assignments to unauthorized members to avoid conflicts + if (row.ipVersion == 4) { + member.ipAssignments.push(blobToIPv4(row.ip)); + ++ipAssignmentCount; + } else if (row.ipVersion == 6) { + member.ipAssignments.push(blobToIPv6(row.ip)); + ++ipAssignmentCount; + } + } + } + },nextStep); + +},function(nextStep) { + + // Old versions only supported Ethertype whitelisting, so that's + // all we mirror forward. The other fields were always unused. + + console.log(' '+ipAssignmentCount+' IP assignments for '+memberCount+' authorized members of '+networkCount+' networks.'); + console.log('Reading allowed Ethernet types (old basic rules)...'); + var etherTypesByNetwork = {}; + old.each('SELECT DISTINCT networkId,ruleNo,etherType FROM Rule WHERE "action" = \'accept\'',function(err,row) { + if (row.networkId in networks) { + var et = parseInt(row.etherType)||0; + var ets = etherTypesByNetwork[row.networkId]; + if (!ets) + etherTypesByNetwork[row.networkId] = [ et ]; + else ets.push(et); + } + },function(err) { + if (err) return nextStep(err); + for(var nwid in etherTypesByNetwork) { + var ets = etherTypesByNetwork[nwid].sort(); + var network = networks[nwid]; + if (network) { + var rules = []; + if (ets.indexOf(0) >= 0) { + // If 0 is in the list, all Ethernet types are allowed so we accept all. + rules.push({ 'type': 'ACTION_ACCEPT' }); + } else { + // Otherwise we whitelist. + for(var i=0;i 0) { + try { + fs.mkdirSync(nwBase+network.id); + } catch (e) {} + var mbase = nwBase+network.id+'/member'; + try { + fs.mkdirSync(mbase,0o700); + } catch (e) {} + mbase = mbase + '/'; + + for(var mi=0;mi", + "license": "GPL-3.0", + "dependencies": { + "async": "^2.1.4", + "sqlite3": "^3.1.8" + } +} diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..6e9fd45 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,24 @@ +zerotier-one (1.2.4-nt1) unstable; urgency=medium + + * Imported Upstream version 1.2.4 + + -- didyouexpectthat Fri, 12 Jan 2018 18:26:28 -0800 + +zerotier-one (1.1.14-nt3) unstable; urgency=medium + + * Clean up authtoken CGI + + -- NAStools Fri, 09 Dec 2016 10:20:55 -0800 + +zerotier-one (1.1.14-nt2) unstable; urgency=medium + + * Include UI from macOS client + * Configure package to use UI from macOS client + + -- NAStools Tue, 29 Nov 2016 18:02:28 -0800 + +zerotier-one (1.1.14-nt1) unstable; urgency=medium + + * Initial release + + -- NAStools Tue, 01 Nov 2016 16:30:45 -0700 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..f11c82a --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 \ No newline at end of file diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..457f46b --- /dev/null +++ b/debian/control @@ -0,0 +1,19 @@ +Source: zerotier-one +Maintainer: NAStools +Section: net +Priority: optional +Standards-Version: 3.9.6 +Build-Depends: debhelper (>= 9), liblz4-dev, libnatpmp-dev, ruby-ronn, dh-systemd +Vcs-Git: git://github.com/didyouexpectthat/zerotierone +Vcs-Browser: https://github.com/didyouexpectthat/zerotierone +Homepage: https://www.zerotier.com + +Package: nastools-zerotier-one +Architecture: any +Depends: readynasos (>= 6.9.2), ${shlibs:Depends}, ${misc:Depends}, + libnatpmp1, iproute2, libstdc++6 +Description: ZeroTier network virtualization service + ZeroTier One lets you join ZeroTier virtual networks and + have them appear as tun/tap ports on your system. See + https://www.zerotier.com/ for instructions and + documentation. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..cd728a0 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,24 @@ +Format: http://dep.debian.net/deps/dep5 +Upstream-Name: zerotier-one +Source: https://github.com/zerotier/ZeroTierOne + +Files: * +Copyright: 2011-2016 ZeroTier, Inc. +License: GPL-3.0+ + +License: GPL-3.0+ + 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 package 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 . + . + On Debian systems, the complete text of the GNU General + Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". diff --git a/debian/gbp.conf b/debian/gbp.conf new file mode 100644 index 0000000..28456f2 --- /dev/null +++ b/debian/gbp.conf @@ -0,0 +1,14 @@ +[DEFAULT] +pristine-tar = True +debian-branch = master +debian-tag = readynas/%(version)s +debian-tag-msg = %(pkg)s ReadyNAS OS release %(version)s + +[git-buildpackage] +export-dir = ../build-area/ +color = on +pristine-tar-commit = True + +[git-dch] +# ignore merge commit messages +git-log = --no-merges diff --git a/debian/install b/debian/install new file mode 100644 index 0000000..ce136e7 --- /dev/null +++ b/debian/install @@ -0,0 +1,6 @@ +debian/readynas/config.xml apps/nastools-zerotier-one +debian/readynas/fvapp-nastools-zerotier-one.service apps/nastools-zerotier-one +debian/readynas/logo.png apps/nastools-zerotier-one +debian/readynas/https.conf apps/nastools-zerotier-one +debian/readynas/authtoken apps/nastools-zerotier-one/cgi +zerotier-one apps/nastools-zerotier-one/sbin diff --git a/debian/links b/debian/links new file mode 100644 index 0000000..7eef662 --- /dev/null +++ b/debian/links @@ -0,0 +1,3 @@ +apps/nastools-zerotier-one/sbin/zerotier-one usr/sbin/zerotier-cli +apps/nastools-zerotier-one/sbin/zerotier-one usr/sbin/zerotier-idtool +apps/nastools-zerotier-one/sbin/zerotier-one usr/sbin/zerotier-one diff --git a/debian/lintian-overrides b/debian/lintian-overrides new file mode 100644 index 0000000..61c7fb5 --- /dev/null +++ b/debian/lintian-overrides @@ -0,0 +1,7 @@ +## Overrides from NAStools +# Overrides for ReadyNAS packaging +nastools-zerotier-one: non-standard-toplevel-dir apps/ +nastools-zerotier-one: file-in-unusual-dir apps/nastools-zerotier-one/* + +# Debian's libminiupnpc is too old, so let ZT include its own +nastools-zerotier-one: embedded-library apps/nastools-zerotier-one/sbin/zerotier-one: libminiupnpc diff --git a/debian/manpages b/debian/manpages new file mode 100644 index 0000000..2f03aa0 --- /dev/null +++ b/debian/manpages @@ -0,0 +1,3 @@ +doc/zerotier-cli.1 +doc/zerotier-idtool.1 +doc/zerotier-one.8 diff --git a/debian/patches/0001-Update-platformDefaultHomePath-for-ReadyNAS-OS.patch b/debian/patches/0001-Update-platformDefaultHomePath-for-ReadyNAS-OS.patch new file mode 100644 index 0000000..9acf2fa --- /dev/null +++ b/debian/patches/0001-Update-platformDefaultHomePath-for-ReadyNAS-OS.patch @@ -0,0 +1,23 @@ +From: NAStools +Date: Tue, 1 Nov 2016 16:09:41 -0700 +Subject: Update platformDefaultHomePath() for ReadyNAS OS + +--- + osdep/OSUtils.cpp | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp +index 3a04308..0a9ade6 100644 +--- a/osdep/OSUtils.cpp ++++ b/osdep/OSUtils.cpp +@@ -244,8 +244,8 @@ std::string OSUtils::platformDefaultHomePath() + // BSD likes /var/db instead of /var/lib + return std::string("/var/db/zerotier-one"); + #else +- // Use /var/lib for Linux and other *nix +- return std::string("/var/lib/zerotier-one"); ++ // Use /apps/nastools-zerotier-one/var for ReadyNAS OS ++ return std::string("/apps/nastools-zerotier-one/var"); + #endif + + #endif diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..e4a416a --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +0001-Update-platformDefaultHomePath-for-ReadyNAS-OS.patch diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..9556cc7 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,14 @@ +#!/bin/sh -e + +case "$1" in + configure) + adduser --system --group --home /var/lib/zerotier-one --no-create-home zerotier-one + ;; +esac + +# avahi explicitly doesn't broadcast over ZeroTier +# so tell avahi to change the config +# avahi already reloads config on file change +sed -i 's/,zt0//' /etc/avahi/avahi-daemon.conf + +#DEBHELPER# diff --git a/debian/readynas/authtoken.c b/debian/readynas/authtoken.c new file mode 100644 index 0000000..5aedb71 --- /dev/null +++ b/debian/readynas/authtoken.c @@ -0,0 +1,21 @@ +#include +#include + +int main() { + FILE *f; + char c[32]; + + f=fopen("/apps/nastools-zerotier-one/var/authtoken.secret","r"); + if(!f) + return 1; + + printf("Content-Type: text/plain\n\n"); + + size_t ret = fread(c, 1, sizeof(c), f); + if(!ret) + return 1; + fwrite(c, ret, 1, stdout); + + fclose(f); + return 0; +} diff --git a/debian/readynas/config.xml b/debian/readynas/config.xml new file mode 100644 index 0000000..84897f9 --- /dev/null +++ b/debian/readynas/config.xml @@ -0,0 +1,13 @@ + + 1.2.4-nt1 + 6.9.2 + ZeroTier One NT + NAStools + 0 + https://localhost/apps/nastools-zerotier-one/ui/ + https://github.com/nastools/zerotierone + nastools-zerotier-one + ZeroTier network virtualization service + fvapp-nastools-zerotier-one.service + + diff --git a/debian/readynas/fvapp-nastools-zerotier-one.service b/debian/readynas/fvapp-nastools-zerotier-one.service new file mode 100644 index 0000000..adc8d71 --- /dev/null +++ b/debian/readynas/fvapp-nastools-zerotier-one.service @@ -0,0 +1,11 @@ +[Unit] +Description=ZeroTier One +After=network.target apache2.service + +[Service] +ExecStart=/apps/nastools-zerotier-one/sbin/zerotier-one +Restart=always +KillMode=process + +[Install] +WantedBy=multi-user.target diff --git a/debian/readynas/https.conf b/debian/readynas/https.conf new file mode 100644 index 0000000..dcd1742 --- /dev/null +++ b/debian/readynas/https.conf @@ -0,0 +1,21 @@ + + LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so + + + LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so + + +ScriptAlias /apps/nastools-zerotier-one/cgi /apps/nastools-zerotier-one/cgi + + + Include "/etc/frontview/apache/Admin_Auth.conf" + + + + + + ProxyPass http://localhost:9993/ + ProxyPassReverse http://localhost:9993/ + + + diff --git a/debian/readynas/logo.png b/debian/readynas/logo.png new file mode 100644 index 0000000..971b24f Binary files /dev/null and b/debian/readynas/logo.png differ diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..6ce46ff --- /dev/null +++ b/debian/rules @@ -0,0 +1,14 @@ +#!/usr/bin/make -f + +%: + dh $@ + +override_dh_auto_build: + make ZT_USE_MINIUPNPC=1 -j$(nproc) + gcc $$(pwd)/debian/readynas/authtoken.c -o $$(pwd)/debian/readynas/authtoken + chmod u+s $$(pwd)/debian/readynas/authtoken + +override_dh_fixperms: + dh_fixperms -Xauthtoken + +override_dh_auto_install: diff --git a/debian/rules.static b/debian/rules.static new file mode 100644 index 0000000..72c5295 --- /dev/null +++ b/debian/rules.static @@ -0,0 +1,16 @@ +#!/usr/bin/make -f + +CFLAGS=-O3 -fstack-protector-strong +CXXFLAGS=-O3 -fstack-protector-strong + +%: + dh $@ --with systemd + +override_dh_auto_build: +# make -j 2 + +override_dh_systemd_start: + dh_systemd_start --restart-after-upgrade + +override_dh_installinit: + dh_installinit --name=zerotier-one -- defaults diff --git a/debian/rules.wheezy.static b/debian/rules.wheezy.static new file mode 100644 index 0000000..0165be3 --- /dev/null +++ b/debian/rules.wheezy.static @@ -0,0 +1,11 @@ +#!/usr/bin/make -f + +CFLAGS=-O3 -fstack-protector +CXXFLAGS=-O3 -fstack-protector + +%: + dh $@ + +override_dh_auto_build: +# make -j 2 + diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..46ebe02 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) \ No newline at end of file diff --git a/debian/source/include-binaries b/debian/source/include-binaries new file mode 100644 index 0000000..997e03f --- /dev/null +++ b/debian/source/include-binaries @@ -0,0 +1 @@ +debian/readynas/logo.png diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..707c64a --- /dev/null +++ b/doc/README.md @@ -0,0 +1,6 @@ +Manual Pages and Other Documentation +===== + +Use "./build.sh" to build the manual pages. + +You'll need either NodeJS/npm installed (script will then automatically install the npm *marked-man* package) or */usr/bin/ronn*. The latter is a Ruby program packaged on some distributions as *rubygem-ronn* or *ruby-ronn* or installable as *gem install ronn*. The Node *marked-man* package and *ronn* from rubygems are two roughly equivalent alternatives for compiling MarkDown into roff/man format. diff --git a/doc/build.sh b/doc/build.sh new file mode 100755 index 0000000..9df72a3 --- /dev/null +++ b/doc/build.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +export PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin + +if [ ! -f zerotier-cli.1.md ]; then + echo 'This script must be run from the doc/ subfolder of the ZeroTier tree.' +fi + +rm -f *.1 *.2 *.8 + +if [ -e /usr/bin/ronn -o -e /usr/local/bin/ronn ]; then + # Use 'ronn' which is available as a package on many distros including Debian + ronn -r zerotier-cli.1.md + ronn -r zerotier-idtool.1.md + ronn -r zerotier-one.8.md +else + # Use 'marked-man' from npm + NODE=/usr/bin/node + if [ ! -e $NODE ]; then + if [ -e /usr/bin/nodejs ]; then + NODE=/usr/bin/nodejs + elif [ -e /usr/local/bin/node ]; then + NODE=/usr/local/bin/node + elif [ -e /usr/local/bin/nodejs ]; then + NODE=/usr/local/bin/nodejs + else + echo 'Unable to find ronn or node/npm -- cannot build man pages!' + exit 1 + fi + fi + + if [ ! -f node_modules/marked-man/bin/marked-man ]; then + echo 'Installing npm package "marked-man" -- MarkDown to ROFF converter...' + npm install marked-man + fi + + $NODE node_modules/marked-man/bin/marked-man zerotier-cli.1.md >zerotier-cli.1 + $NODE node_modules/marked-man/bin/marked-man zerotier-idtool.1.md >zerotier-idtool.1 + $NODE node_modules/marked-man/bin/marked-man zerotier-one.8.md >zerotier-one.8 +fi + +exit 0 diff --git a/doc/contact@zerotier.com.gpg b/doc/contact@zerotier.com.gpg new file mode 100644 index 0000000..dc7d645 --- /dev/null +++ b/doc/contact@zerotier.com.gpg @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: GPGTools - https://gpgtools.org + +mQINBFdQq7oBEADEVhyRiaL8dEjMPlI/idO8tA7adjhfvejxrJ3Axxi9YIuIKhWU +5hNjDjZAiV9iSCMfJN3TjC3EDA+7nFyU6nDKeAMkXPbaPk7ti+Tb1nA4TJsBfBlm +CC14aGWLItpp8sI00FUzorxLWRmU4kOkrRUJCq2kAMzbYWmHs0hHkWmvj8gGu6mJ +WU3sDIjvdsm3hlgtqr9grPEnj+gA7xetGs3oIfp6YDKymGAV49HZmVAvSeoqfL1p +pEKlNQ1aO9uNfHLdx6+4pS1miyo7D1s7ru2IcqhTDhg40cHTL/VldC3d8vXRFLIi +Uo2tFZ6J1jyQP5c1K4rTpw3UNVne3ob7uCME+T1+ePeuM5Y/cpcCvAhJhO0rrlr0 +dP3lOKrVdZg4qhtFAspC85ivcuxWNWnfTOBrgnvxCA1fmBX+MLNUEDsuu55LBNQT +5+WyrSchSlsczq+9EdomILhixUflDCShHs+Efvh7li6Pg56fwjEfj9DJYFhRvEvQ +7GZ7xtysFzx4AYD4/g5kCDsMTbc9W4Jv+JrMt3JsXt2zqwI0P4R1cIAu0J6OZ4Xa +dJ7Ci1WisQuJRcCUtBTUxcYAClNGeors5Nhl4zDrNIM7zIJp+GfPYdWKVSuW10mC +r3OS9QctMSeVPX/KE85TexeRtmyd4zUdio49+WKgoBhM8Z9MpTaafn2OPQARAQAB +tFBaZXJvVGllciwgSW5jLiAoWmVyb1RpZXIgU3VwcG9ydCBhbmQgUmVsZWFzZSBT +aWduaW5nIEtleSkgPGNvbnRhY3RAemVyb3RpZXIuY29tPokCNwQTAQoAIQUCV1Cr +ugIbAwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRAWVxmII+UqYViGEACnC3+3 +lRzfv7f7JLWo23FSHjlF3IiWfYd+47BLDx706SDih1H6Qt8CqRy706bWbtictEJ/ +xTaWgTEDzY/lRalYO5NAFTgK9h2zBP1t8zdEA/rmtVPOWOzd6jr0q3l3pKQTeMF0 +6g+uaMDG1OkBz6MCwdg9counz6oa8OHK76tXNIBEnGOPBW375z1O+ExyddQOHDcS +IIsUlFmtIL1yBa7Q5NSfLofPLfS0/o2FItn0riSaAh866nXHynQemjTrqkUxf5On +65RLM+AJQaEkX17vDlsSljHrtYLKrhEueqeq50e89c2Ya4ucmSVeC9lrSqfyvGOO +P3aT/hrmeE9XBf7a9vozq7XhtViEC/ZSd1/z/oeypv4QYenfw8CtXP5bW1mKNK/M +8xnrnYwo9BUMclX2ZAvu1rTyiUvGre9fEGfhlS0rjmCgYfMgBZ+R/bFGiNdn6gAd +PSY/8fP8KFZl0xUzh2EnWe/bptoZ67CKkDbVZnfWtuKA0Ui7anitkjZiv+6wanv4 ++5A3k/H3D4JofIjRNgx/gdVPhJfWjAoutIgGeIWrkfcAP9EpsR5swyc4KuE6kJ/Y +wXXVDQiju0xE1EdNx/S1UOeq0EHhOFqazuu00ojATekUPWenNjPWIjBYQ0Ag4ycL +KU558PFLzqYaHphdWYgxfGR+XSgzVTN1r7lW87kCDQRXUKu6ARAA2wWOywNMzEiP +ZK6CqLYGZqrpfx+drOxSowwfwjP3odcK8shR/3sxOmYVqZi0XVZtb9aJVz578rNb +e4Vfugql1Yt6w3V84z/mtfj6ZbTOOU5yAGZQixm6fkXAnpG5Eer/C8Aw8dH1EreP +Na1gIVcUzlpg2Ql23qjr5LqvGtUB4BqJSF4X8efNi/y0hj/GaivUMqCF6+Vvh3GG +fhvzhgBPku/5wK2XwBL9BELqaQ/tWOXuztMw0xFH/De75IH3LIvQYCuv1pnM4hJL +XYnpAGAWfmFtmXNnPVon6g542Z6c0G/qi657xA5vr6OSSbazDJXNiHXhgBYEzRrH +napcohTQwFKEA3Q4iftrsTDX/eZVTrO9x6qKxwoBVTGwSE52InWAxkkcnZM6tkfV +n7Ukc0oixZ6E70Svls27zFgaWbUFJQ6JFoC6h+5AYbaga6DwKCYOP3AR+q0ZkcH/ +oJIdvKuhF9zDZbQhd76b4gK3YXnMpVsj9sQ9P23gh61RkAQ1HIlGOBrHS/XYcvpk +DcfIlJXKC3V1ggrG+BpKu46kiiYmRR1/yM0EXH2n99XhLNSxxFxxWhjyw8RcR6iG +ovDxWAULW+bJHjaNJdgb8Kab7j2nT2odUjUHMP42uLJgvS5LgRn39IvtzjoScAqg +8I817m8yLU/91D2f5qmJIwFI6ELwImkAEQEAAYkCHwQYAQoACQUCV1CrugIbDAAK +CRAWVxmII+UqYWSSEACxaR/hhr8xUIXkIV52BeD+2BOS8FNOi0aM67L4fEVplrsV +Op9fvAnUNmoiQo+RFdUdaD2Rpq+yUjQHHbj92mlk6Cmaon46wU+5bAWGYpV1Uf+o +wbKw1Xv83Uj9uHo7zv9WDtOUXUiTe/S792icTfRYrKbwkfI8iCltgNhTQNX0lFX/ +Sr2y1/dGCTCMEuA/ClqGKCm9lIYdu+4z32V9VXTSX85DsUjLOCO/hl9SHaelJgmi +IJzRY1XLbNDK4IH5eWtbaprkTNIGt00QhsnM5w+rn1tO80giSxXFpKBE+/pAx8PQ +RdVFzxHtTUGMCkZcgOJolk8y+DJWtX8fP+3a4Vq11a3qKJ19VXk3qnuC1aeW7OQF +j6ISyHsNNsnBw5BRaS5tdrpLXw6Z7TKr1eq+FylmoOK0pIw5xOdRmSVoFm4lVcI5 +e5EwB7IIRF00IFqrXe8dCT0oDT9RXc6CNh6GIs9D9YKwDPRD/NKQlYoegfa13Jz7 +S3RIXtOXudT1+A1kaBpGKnpXOYD3w7jW2l0zAd6a53AAGy4SnL1ac4cml76NIWiF +m2KYzvMJZBk5dAtFa0SgLK4fg8X6Ygoo9E0JsXxSrW9I1JVfo6Ia//YOBMtt4XuN +Awqahjkq87yxOYYTnJmr2OZtQuFboymfMhNqj3G2DYmZ/ZIXXPgwHx0fnd3R0Q== +=JgAv +-----END PGP PUBLIC KEY BLOCK----- diff --git a/doc/ext/kubernetes/.zerotierCliSettings b/doc/ext/kubernetes/.zerotierCliSettings new file mode 100644 index 0000000..0e7df9b --- /dev/null +++ b/doc/ext/kubernetes/.zerotierCliSettings @@ -0,0 +1,18 @@ +{ + "configVersion": 1, + "defaultCentral": "@my.zerotier.com", + "defaultController": "@my.zerotier.com", + "defaultOne": "@local", + "things": { + "local": { + "auth": "local_service_auth_token_replaced_automatically", + "type": "one", + "url": "http://127.0.0.1:9993/" + }, + "my.zerotier.com": { + "auth": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "type": "central", + "url": "https://my.zerotier.com/" + } + } +} diff --git a/doc/ext/kubernetes/Dockerfile b/doc/ext/kubernetes/Dockerfile new file mode 100644 index 0000000..6437a2b --- /dev/null +++ b/doc/ext/kubernetes/Dockerfile @@ -0,0 +1,19 @@ +FROM node:4.4 +EXPOSE 8080/tcp 9993/udp + +# Install ZT network conf files +RUN mkdir -p /var/lib/zerotier-one/networks.d +ADD *.conf /var/lib/zerotier-one/networks.d/ +ADD *.conf / +ADD zerotier-one / +ADD zerotier-cli / +ADD .zerotierCliSettings / + +# Install App +ADD server.js / + +# script which will start/auth VM on ZT network +ADD entrypoint.sh / +RUN chmod -v +x /entrypoint.sh + +CMD ["./entrypoint.sh"] \ No newline at end of file diff --git a/doc/ext/kubernetes/README.md b/doc/ext/kubernetes/README.md new file mode 100644 index 0000000..482e77e --- /dev/null +++ b/doc/ext/kubernetes/README.md @@ -0,0 +1,150 @@ +Kubernetes + ZeroTier +==== + +A self-authorizing Kubernetes cluster deployment over a private ZeroTier network. + +This is a quick tutorial for setting up a Kubernetes deployment which can self-authorize each new replica onto your private ZeroTier network with no additional configuration needed when you scale. The Kubernetes-specific instructions and content is based on the [hellonode](http://kubernetes.io/docs/hellonode/) tutorial. All of the files discussed below can be found [here](); + + + +## Preliminary tasks + +**Step 1: Go to [my.zerotier.com](https://my.zerotier.com) and generate a network controller API key. This key will be used by ZeroTier to automatically authorize new instances of your VMs to join your secure deployment network during replication.** + +**Step 2: Create a new `private` network. Take note of the network ID, henceforth: `nwid`** + +**Step 3: Follow the instructions from the [hellonode](ttp://kubernetes.io/docs/hellonode/) tutorial to set up your development system.** + +*** +## Construct docker image + +**Step 4: Create necessary files for inclusion into image, your resultant directory should contain:** + + - `ztkube/.conf` + - `ztkube/Dockerfile` + - `ztkube/entrypoint.sh` + - `ztkube/server.js` + - `ztkube/zerotier-cli` + - `ztkube/zerotier-one` + +Start by creating a build directory to copy all required files into `mkdir ztkube`. Then build the following: + - `make one` + - `make cli` + +Add the following files to the `ztkube` directory. These files will be compiled into the Docker image. + + - Create an empty `.conf` file to specify the private deployment network you created in *Step 2*: + + - Create a CLI tool config file `.zerotierCliSettings` which should only contain your network controller API key to authorize new devices on your network (the local service API key will be filled in automatically). In this example the default controller is hosted by us at [my.zerotier.com](https://my.zerotier.com). Alternatively, you can host your own network controller but you'll need to modify the CLI config file accordingly. + +``` +{ + "configVersion": 1, + "defaultCentral": "@my.zerotier.com", + "defaultController": "@my.zerotier.com", + "defaultOne": "@local", + "things": { + "local": { + "auth": "local_service_auth_token_replaced_automatically", + "type": "one", + "url": "http://127.0.0.1:9993/" + }, + "my.zerotier.com": { + "auth": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "type": "central", + "url": "https://my.zerotier.com/" + } + } +} +``` + + + - Create a `Dockerfile` which will copy the ZeroTier service as well as the ZeroTier CLI to the image: + +``` +FROM node:4.4 +EXPOSE 8080/tcp 9993/udp + +# Install ZT network conf files +RUN mkdir -p /var/lib/zerotier-one/networks.d +ADD *.conf /var/lib/zerotier-one/networks.d/ +ADD *.conf / +ADD zerotier-one / +ADD zerotier-cli / +ADD .zerotierCliSettings / + +# Install App +ADD server.js / + +# script which will start/auth VM on ZT network +ADD entrypoint.sh / +RUN chmod -v +x /entrypoint.sh + +CMD ["./entrypoint.sh"] +``` + + - Create the `entrypoint.sh` script which will start the ZeroTier service in the VM, attempt to join your deployment network and automatically authorize the new VM if your network is set to private: + +``` +#!/bin/bash + +echo '*** ZeroTier-Kubernetes self-auth test script' +chown -R daemon /var/lib/zerotier-one +chgrp -R daemon /var/lib/zerotier-one +su daemon -s /bin/bash -c '/zerotier-one -d -U -p9993 >>/tmp/zerotier-one.out 2>&1' +dev="" +nwconf=$(ls *.conf) +nwid="${nwconf%.*}" + +sleep 10 +dev=$(cat /var/lib/zerotier-one/identity.public| cut -d ':' -f 1) + +echo '*** Joining' +./zerotier-cli join "$nwid".conf +# Fill out local service auth token +AUTHTOKEN=$(cat /var/lib/zerotier-one/authtoken.secret) +sed "s|\local_service_auth_token_replaced_automatically|${AUTHTOKEN}|" .zerotierCliSettings > /root/.zerotierCliSettings +echo '*** Authorizing' +./zerotier-cli net-auth @my.zerotier.com "$nwid" "$dev" +echo '*** Cleaning up' # Remove controller auth token +rm -rf .zerotierCliSettings /root/.zerotierCliSettings +node server.js +``` + +**Step 5: Build the image:** + + - `docker build -t gcr.io/$PROJECT_ID/hello-node .` + + + +**Step 6: Push the docker image to your *Container Registry*** + + - `gcloud docker push gcr.io/$PROJECT_ID/hello-node:v1` + +*** +## Deploy! + +**Step 7: Create Kubernetes Cluster** + + - `gcloud config set compute/zone us-central1-a` + + - `gcloud container clusters create hello-world` + + - `gcloud container clusters get-credentials hello-world` + + + +**Step 8: Create your pod** + + - `kubectl run hello-node --image=gcr.io/$PROJECT_ID/hello-node:v1 --port=8080` + + + +**Step 9: Scale** + + - `kubectl scale deployment hello-node --replicas=4` + +*** +## Verify + +Now, after a minute or so you can use `zerotier-cli net-members ` to show all of your VM instances on your ZeroTier deployment network. If you haven't [configured your local CLI](https://github.com/zerotier/ZeroTierOne/tree/dev/cli), you can simply log into [my.zerotier.com](https://my.zerotier.com), go to *Networks -> nwid* to check that your VMs are indeed members of your private network. You should also note that the `entrypoint.sh` script will automatically delete your network controller API key once it has authorized your VM. This is merely a security measure and can be removed if needed. diff --git a/doc/ext/kubernetes/entrypoint.sh b/doc/ext/kubernetes/entrypoint.sh new file mode 100644 index 0000000..80cd278 --- /dev/null +++ b/doc/ext/kubernetes/entrypoint.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +echo '*** ZeroTier-Kubernetes self-auth test script' +chown -R daemon /var/lib/zerotier-one +chgrp -R daemon /var/lib/zerotier-one +su daemon -s /bin/bash -c '/zerotier-one -d -U -p9993 >>/tmp/zerotier-one.out 2>&1' +dev="" +nwconf=$(ls *.conf) +nwid="${nwconf%.*}" + +sleep 10 +dev=$(cat /var/lib/zerotier-one/identity.public| cut -d ':' -f 1) + +echo '*** Joining' +./zerotier-cli join "$nwid".conf +# Fill out local service auth token +AUTHTOKEN=$(cat /var/lib/zerotier-one/authtoken.secret) +sed "s|\local_service_auth_token_replaced_automatically|${AUTHTOKEN}|" .zerotierCliSettings > /root/.zerotierCliSettings +echo '*** Authorizing' +./zerotier-cli net-auth @my.zerotier.com "$nwid" "$dev" +echo '*** Cleaning up' # Remove controller auth token +rm -rf .zerotierCliSettings /root/.zerotierCliSettings +node server.js \ No newline at end of file diff --git a/doc/ext/kubernetes/server.js b/doc/ext/kubernetes/server.js new file mode 100644 index 0000000..a4b08bb --- /dev/null +++ b/doc/ext/kubernetes/server.js @@ -0,0 +1,8 @@ +var http = require('http'); +var handleRequest = function(request, response) { + console.log('Received request for URL: ' + request.url); + response.writeHead(200); + response.end('Hello World!'); +}; +var www = http.createServer(handleRequest); +www.listen(8080); diff --git a/doc/manpage_encoding_declaration.UTF-8 b/doc/manpage_encoding_declaration.UTF-8 new file mode 100644 index 0000000..991db0a --- /dev/null +++ b/doc/manpage_encoding_declaration.UTF-8 @@ -0,0 +1 @@ +'\" -*- coding: utf-8 -*- diff --git a/doc/zerotier-cli.1 b/doc/zerotier-cli.1 new file mode 100644 index 0000000..167109e --- /dev/null +++ b/doc/zerotier-cli.1 @@ -0,0 +1,83 @@ +.TH "ZEROTIER\-CLI" "1" "December 2016" "" "" +.SH "NAME" +\fBzerotier-cli\fR \- control local ZeroTier virtual network service +.SH SYNOPSIS +.P +\fBzerotier\-cli\fP [\-switches] [arguments] +.SH DESCRIPTION +.P +\fBzerotier\-cli\fR provides a simple command line interface to the local JSON API of the ZeroTier virtual network endpoint service zerotier\-one(8)\. +.P +By default \fBzerotier\-cli\fR must be run as root or with \fBsudo\fP\|\. If you want to allow an unprivileged user to use \fBzerotier\-cli\fR to control the system ZeroTier service, you can create a local copy of the ZeroTier service authorization token in the user's home directory: +.P +.RS 2 +.nf +sudo cp /var/lib/zerotier\-one/authtoken\.secret /home/user/\.zeroTierOneAuthToken +chown user /home/user/\.zeroTierOneAuthToken +chmod 0600 /home/user/\.zeroTierOneAuthToken +.fi +.RE +.P +(The location of ZeroTier's service home may differ by platform\. See zerotier\-one(8)\.) +.P +Note that this gives the user the power to connect or disconnect the system to or from any virtual network, which is a significant permission\. +.P +\fBzerotier\-cli\fR has several command line arguments that are visible in \fBhelp\fP output\. The two most commonly used are \fB\-j\fP for raw JSON output and \fB\-D\fP to specify an alternative ZeroTier service working directory\. Raw JSON output is easier to parse in scripts and also contains verbose details not present in the tabular output\. The \fB\-D\fP option specifies where the service's zerotier\-one\.port and authtoken\.secret files are located if the service is not running at the default location for your system\. +.SH COMMANDS +.RS 0 +.IP \(bu 2 +\fBhelp\fP: +Displays \fBzerotier\-cli\fR help\. +.IP \(bu 2 +\fBinfo\fP: +Shows information about this device including its 10\-digit ZeroTier address and apparent connection status\. Use \fB\-j\fP for more verbose output\. +.IP \(bu 2 +\fBlistpeers\fP: +This command lists the ZeroTier VL1 (virtual layer 1, the peer to peer network) peers this service knows about and has recently (within the past 30 minutes or so) communicated with\. These are not necessarily all the devices on your virtual network(s), and may also include a few devices not on any virtual network you've joined\. These are typically either root servers or network controllers\. +.IP \(bu 2 +\fBlistnetworks\fP: +This lists the networks your system belongs to and some information about them, such as any ZeroTier\-managed IP addresses you have been assigned\. (IP addresses assigned manually to ZeroTier interfaces will not be listed here\. Use the standard network interface commands to see these\.) +.IP \(bu 2 +\fBjoin\fP: +To join a network just use \fBjoin\fP and its 16\-digit hex network ID\. That's it\. Then use \fBlistnetworks\fP to see the status\. You'll either get a reply from the network controller with a certificate and other info such as IP assignments, or you'll get "access denied\." In this case you'll need the administrator of this network to authorize your device by its 10\-digit device ID (visible with \fBinfo\fP) on the network's controller\. +.IP \(bu 2 +\fBleave\fP: +Leaving a network is as easy as joining it\. This disconnects from the network and deletes its interface from the system\. Note that peers on the network may hang around in \fBlistpeers\fP for up to 30 minutes until they time out due to lack of traffic\. But if they no longer share a network with you, they can't actually communicate with you in any meaningful way\. + +.RE +.SH EXAMPLES +.P +Join "Earth," ZeroTier's big public party line network: +.P +.RS 2 +.nf +$ sudo zerotier\-cli join 8056c2e21c000001 +$ sudo zerotier\-cli listnetworks +( wait until you get an Earth IP ) +$ ping earth\.zerotier\.net +( you should now be able to ping our Earth test IP ) +.fi +.RE +.P +Leave "Earth": +.P +.RS 2 +.nf +$ sudo zerotier\-cli leave 8056c2e21c000001 +.fi +.RE +.P +List VL1 peers: +.P +.RS 2 +.nf +$ sudo zerotier\-cli listpeers +.fi +.RE +.SH COPYRIGHT +.P +(c)2011\-2016 ZeroTier, Inc\. \-\- https://www\.zerotier\.com/ \-\- https://github\.com/zerotier +.SH SEE ALSO +.P +zerotier\-one(8), zerotier\-idtool(1) + diff --git a/doc/zerotier-cli.1.md b/doc/zerotier-cli.1.md new file mode 100644 index 0000000..6252d45 --- /dev/null +++ b/doc/zerotier-cli.1.md @@ -0,0 +1,68 @@ +zerotier-cli(1) -- control local ZeroTier virtual network service +================================================================= + +## SYNOPSIS + +`zerotier-cli` [-switches] [arguments] + +## DESCRIPTION + +**zerotier-cli** provides a simple command line interface to the local JSON API of the ZeroTier virtual network endpoint service zerotier-one(8). + +By default **zerotier-cli** must be run as root or with `sudo`. If you want to allow an unprivileged user to use **zerotier-cli** to control the system ZeroTier service, you can create a local copy of the ZeroTier service authorization token in the user's home directory: + + sudo cp /var/lib/zerotier-one/authtoken.secret /home/user/.zeroTierOneAuthToken + chown user /home/user/.zeroTierOneAuthToken + chmod 0600 /home/user/.zeroTierOneAuthToken + +(The location of ZeroTier's service home may differ by platform. See zerotier-one(8).) + +Note that this gives the user the power to connect or disconnect the system to or from any virtual network, which is a significant permission. + +**zerotier-cli** has several command line arguments that are visible in `help` output. The two most commonly used are `-j` for raw JSON output and `-D` to specify an alternative ZeroTier service working directory. Raw JSON output is easier to parse in scripts and also contains verbose details not present in the tabular output. The `-D` option specifies where the service's zerotier-one.port and authtoken.secret files are located if the service is not running at the default location for your system. + +## COMMANDS + + * `help`: + Displays **zerotier-cli** help. + + * `info`: + Shows information about this device including its 10-digit ZeroTier address and apparent connection status. Use `-j` for more verbose output. + + * `listpeers`: + This command lists the ZeroTier VL1 (virtual layer 1, the peer to peer network) peers this service knows about and has recently (within the past 30 minutes or so) communicated with. These are not necessarily all the devices on your virtual network(s), and may also include a few devices not on any virtual network you've joined. These are typically either root servers or network controllers. + + * `listnetworks`: + This lists the networks your system belongs to and some information about them, such as any ZeroTier-managed IP addresses you have been assigned. (IP addresses assigned manually to ZeroTier interfaces will not be listed here. Use the standard network interface commands to see these.) + + * `join`: + To join a network just use `join` and its 16-digit hex network ID. That's it. Then use `listnetworks` to see the status. You'll either get a reply from the network controller with a certificate and other info such as IP assignments, or you'll get "access denied." In this case you'll need the administrator of this network to authorize your device by its 10-digit device ID (visible with `info`) on the network's controller. + + * `leave`: + Leaving a network is as easy as joining it. This disconnects from the network and deletes its interface from the system. Note that peers on the network may hang around in `listpeers` for up to 30 minutes until they time out due to lack of traffic. But if they no longer share a network with you, they can't actually communicate with you in any meaningful way. + +## EXAMPLES + +Join "Earth," ZeroTier's big public party line network: + + $ sudo zerotier-cli join 8056c2e21c000001 + $ sudo zerotier-cli listnetworks + ( wait until you get an Earth IP ) + $ ping earth.zerotier.net + ( you should now be able to ping our Earth test IP ) + +Leave "Earth": + + $ sudo zerotier-cli leave 8056c2e21c000001 + +List VL1 peers: + + $ sudo zerotier-cli listpeers + +## COPYRIGHT + +(c)2011-2016 ZeroTier, Inc. -- https://www.zerotier.com/ -- https://github.com/zerotier + +## SEE ALSO + +zerotier-one(8), zerotier-idtool(1) diff --git a/doc/zerotier-idtool.1 b/doc/zerotier-idtool.1 new file mode 100644 index 0000000..fbc367a --- /dev/null +++ b/doc/zerotier-idtool.1 @@ -0,0 +1,84 @@ +.TH "ZEROTIER\-IDTOOL" "1" "December 2016" "" "" +.SH "NAME" +\fBzerotier-idtool\fR \- tool for creating and manipulating ZeroTier identities +.SH SYNOPSIS +.P +\fBzerotier\-idtool\fP [args] +.SH DESCRIPTION +.P +\fBzerotier\-idtool\fR is a command line utility for doing things with ZeroTier identities\. A ZeroTier identity consists of a public/private key pair (or just the public if it's only an identity\.public) and a 10\-digit hexadecimal ZeroTier address derived from the public key by way of a proof of work based hash function\. +.SH COMMANDS +.P +When command arguments call for a public or secret (full) identity, the identity can be specified as a path to a file or directly on the command line\. +.RS 0 +.IP \(bu 2 +\fBhelp\fP: +Display help\. (Also running with no command does this\.) +.IP \(bu 2 +\fBgenerate\fP [secret file] [public file] [vanity]: +Generate a new ZeroTier identity\. If a secret file is specified, the full identity including the private key will be written to this file\. If the public file is specified, the public portion will be written there\. If no file paths are specified the full secret identity is output to STDOUT\. The vanity prefix is a series of hexadecimal digits that the generated identity's address should start with\. Typically this isn't used, and if it's specified generation can take a very long time due to the intrinsic cost of generating identities with their proof of work function\. Generating an identity with a known 16\-bit (4 digit) prefix on a 2\.8ghz Core i5 (using one core) takes an average of two hours\. +.IP \(bu 2 +\fBvalidate\fP : +Locally validate an identity's key and proof of work function correspondence\. +.IP \(bu 2 +\fBgetpublic\fP : +Extract the public portion of an identity\.secret and print to STDOUT\. +.IP \(bu 2 +\fBsign\fP : +Sign a file's contents with SHA512+ECC\-256 (ed25519)\. The signature is output in hex to STDOUT\. +.IP \(bu 2 +\fBverify\fP : +Verify a signature created with \fBsign\fP\|\. +.IP \(bu 2 +\fBmkcom\fP [id,value,maxdelta] [\|\.\.\.]: +Create and sign a network membership certificate\. This is not generally useful since network controllers do this automatically and is included mostly for testing purposes\. + +.RE +.SH EXAMPLES +.P +Generate and dump a new identity: +.P +.RS 2 +.nf +$ zerotier\-idtool generate +.fi +.RE +.P +Generate and write a new identity, both secret and public parts: +.P +.RS 2 +.nf +$ zerotier\-idtool generate identity\.secret identity\.public +.fi +.RE +.P +Generate a vanity address that begins with the hex digits "beef" (this will take a while!): +.P +.RS 2 +.nf +$ zerotier\-idtool generate beef\.secret beef\.public beef +.fi +.RE +.P +Sign a file with an identity's secret key: +.P +.RS 2 +.nf +$ zerotier\-idtool sign identity\.secret last_will_and_testament\.txt +.fi +.RE +.P +Verify a file's signature with a public key: +.P +.RS 2 +.nf +$ zerotier\-idtool verify identity\.public last_will_and_testament\.txt +.fi +.RE +.SH COPYRIGHT +.P +(c)2011\-2016 ZeroTier, Inc\. \-\- https://www\.zerotier\.com/ \-\- https://github\.com/zerotier +.SH SEE ALSO +.P +zerotier\-one(8), zerotier\-cli(1) + diff --git a/doc/zerotier-idtool.1.md b/doc/zerotier-idtool.1.md new file mode 100644 index 0000000..52a586c --- /dev/null +++ b/doc/zerotier-idtool.1.md @@ -0,0 +1,65 @@ +zerotier-idtool(1) -- tool for creating and manipulating ZeroTier identities +============================================================================ + +## SYNOPSIS + +`zerotier-idtool` [args] + +## DESCRIPTION + +**zerotier-idtool** is a command line utility for doing things with ZeroTier identities. A ZeroTier identity consists of a public/private key pair (or just the public if it's only an identity.public) and a 10-digit hexadecimal ZeroTier address derived from the public key by way of a proof of work based hash function. + +## COMMANDS + +When command arguments call for a public or secret (full) identity, the identity can be specified as a path to a file or directly on the command line. + + * `help`: + Display help. (Also running with no command does this.) + + * `generate` [secret file] [public file] [vanity]: + Generate a new ZeroTier identity. If a secret file is specified, the full identity including the private key will be written to this file. If the public file is specified, the public portion will be written there. If no file paths are specified the full secret identity is output to STDOUT. The vanity prefix is a series of hexadecimal digits that the generated identity's address should start with. Typically this isn't used, and if it's specified generation can take a very long time due to the intrinsic cost of generating identities with their proof of work function. Generating an identity with a known 16-bit (4 digit) prefix on a 2.8ghz Core i5 (using one core) takes an average of two hours. + + * `validate` : + Locally validate an identity's key and proof of work function correspondence. + + * `getpublic` : + Extract the public portion of an identity.secret and print to STDOUT. + + * `sign` : + Sign a file's contents with SHA512+ECC-256 (ed25519). The signature is output in hex to STDOUT. + + * `verify` : + Verify a signature created with `sign`. + + * `mkcom` [id,value,maxdelta] [...]: + Create and sign a network membership certificate. This is not generally useful since network controllers do this automatically and is included mostly for testing purposes. + +## EXAMPLES + +Generate and dump a new identity: + + $ zerotier-idtool generate + +Generate and write a new identity, both secret and public parts: + + $ zerotier-idtool generate identity.secret identity.public + +Generate a vanity address that begins with the hex digits "beef" (this will take a while!): + + $ zerotier-idtool generate beef.secret beef.public beef + +Sign a file with an identity's secret key: + + $ zerotier-idtool sign identity.secret last_will_and_testament.txt + +Verify a file's signature with a public key: + + $ zerotier-idtool verify identity.public last_will_and_testament.txt + +## COPYRIGHT + +(c)2011-2016 ZeroTier, Inc. -- https://www.zerotier.com/ -- https://github.com/zerotier + +## SEE ALSO + +zerotier-one(8), zerotier-cli(1) diff --git a/doc/zerotier-one.8 b/doc/zerotier-one.8 new file mode 100644 index 0000000..4ad7a15 --- /dev/null +++ b/doc/zerotier-one.8 @@ -0,0 +1,104 @@ +.TH "ZEROTIER\-ONE" "8" "December 2016" "" "" +.SH "NAME" +\fBzerotier-one\fR \- ZeroTier virtual network endpoint service +.SH SYNOPSIS +.P +\fBzerotier\-one\fP [\-switches] [working directory] +.SH DESCRIPTION +.P +\fBzerotier\-one\fR is the service/daemon responsible for connecting a Unix (Linux/BSD/OSX) system to one or more ZeroTier virtual networks and presenting those networks to the system as virtual network ports\. You can think of it as a peer to peer VPN client\. +.P +It's typically run by init systems like systemd (Linux) or launchd (Mac) rather than directly by the user, and it must be run as root unless you give it the \fB\-U\fP switch and don't plan on actually joining networks (e\.g\. to run a network controller microservice only)\. +.P +The \fBzerotier\-one\fR service keeps its state and other files in a working directory\. If this directory is not specified at launch it defaults to "/var/lib/zerotier\-one" on Linux, "/Library/Application Support/ZeroTier/One" on Mac, and "/var/db/zerotier\-one" on FreeBSD and other similar BSDs\. The working directory should persist\. It shouldn't be automatically cleaned by system cleanup daemons or stored in a volatile location\. Loss of its identity\.secret file results in loss of this system's unique 10\-digit ZeroTier address and key\. +.P +Multiple instances of \fBzerotier\-one\fR can be run on the same system as long as they are run with different primary ports (see switches) and a different working directory\. But since a single service can join any number of networks, typically there's no point in doing this\. +.P +The \fBzerotier\-one\fR service is controlled via a JSON API available at 127\.0\.0\.1: with the default primary port being 9993\. Access to this API requires an authorization token normally found in the authtoken\.secret file in the service's working directory\. On some platforms access may be guarded by other measures such as socket peer UID/GID lookup if additional security options are enabled (this is not the default)\. +.P +The first time the service is started in a fresh working directory, it generates a ZeroTier identity\. On slow systems this process can take ten seconds or more due to an anti\-DDOS/anti\-counterfeit proof of work function used by ZeroTier in address generation\. This only happens once, and once generated the result is saved in identity\.secret in the working directory\. This file represents and defines/claims your ZeroTier address and associated ECC\-256 key pair\. +.SH SWITCHES +.RS 0 +.IP \(bu 2 +\fB\-h\fP: +Display help\. +.IP \(bu 2 +\fB\-v\fP: +Display ZeroTier One version\. +.IP \(bu 2 +\fB\-U\fP: +Skip privilege check and allow to be run by non\-privileged user\. This is typically used when \fBzerotier\-one\fR is built with the network controller option included\. In this case the ZeroTier service might only be acting as a network controller and might never actually join networks, in which case it does not require elevated system permissions\. +.IP \(bu 2 +\fB\-p\fP: +Specify a different primary port\. If this is not given the default is 9993\. If zero is given a random port is chosen each time\. +.IP \(bu 2 +\fB\-d\fP: +Fork and run as a daemon\. +.IP \(bu 2 +\fB\-i\fP: +Invoke the \fBzerotier\-idtool\fR personality, in which case the binary behaves like zerotier\-idtool(1)\. This happens automatically if the name of the binary (or a symlink to it) is zerotier\-idtool\. +.IP \(bu 2 +\fB\-q\fP: +Invoke the \fBzerotier\-cli\fR personality, in which case the binary behaves like zerotier\-cli(1)\. This happens automatically if the name of the binary (or a symlink to it) is zerotier\-cli\. + +.RE +.SH EXAMPLES +.P +Run as daemon with OS default working directory and default port: +.P +.RS 2 +.nf +$ sudo zerotier\-one \-d +.fi +.RE +.P +Run as daemon with a different working directory and port: +.P +.RS 2 +.nf +$ sudo zerotier\-one \-d \-p12345 /tmp/zerotier\-working\-directory\-test +.fi +.RE +.SH FILES +.P +These are found in the service's working directory\. +.RS 0 +.IP \(bu 2 +\fBidentity\.public\fP: +The public portion of your ZeroTier identity, which is your 10\-digit hex address and the associated public key\. +.IP \(bu 2 +\fBidentity\.secret\fP: +Your full ZeroTier identity including its private key\. This file identifies the system on the network, which means you can move a ZeroTier address around by copying this file and you should back up this file if you want to save your system's static ZeroTier address\. This file must be protected, since theft of its secret key will allow anyone to impersonate your device on any network and decrypt traffic\. For network controllers this file is particularly sensitive since it constitutes the private key for a certificate authority for the controller's networks\. +.IP \(bu 2 +\fBauthtoken\.secret\fP: +The secret token used to authenticate requests to the service's local JSON API\. If it does not exist it is generated from a secure random source on service start\. To use, send it in the "X\-ZT1\-Auth" header with HTTP requests to 127\.0\.0\.1:\|\. +.IP \(bu 2 +\fBdevicemap\fP: +Remembers mappings of zt# interface numbers to ZeroTier networks so they'll persist across restarts\. On some systems that support longer interface names that can encode the network ID (such as FreeBSD) this file may not be present\. +.IP \(bu 2 +\fBzerotier\-one\.pid\fP: +ZeroTier's PID\. This file is deleted on normal shutdown\. +.IP \(bu 2 +\fBzerotier\-one\.port\fP: +ZeroTier's primary port, which is also where its JSON API is found at 127\.0\.0\.1:\|\. This file is created on startup and is read by zerotier\-cli(1) to determine where it should find the control API\. +.IP \(bu 2 +\fBcontroller\.db\fP: +If the ZeroTier One service is built with the network controller enabled, this file contains the controller's SQLite3 database\. +.IP \(bu 2 +\fBcontroller\.db\.backup\fP: +If the ZeroTier One service is built with the network controller enabled, it periodically backs up its controller\.db database in this file (currently every 5 minutes if there have been changes)\. Since this file is not a currently in use SQLite3 database it's safer to back up without corruption\. On new backups the file is rotated out rather than being rewritten in place\. +.IP \(bu 2 +\fBiddb\.d/\fP (directory): +Caches the public identity of every peer ZeroTier has spoken with in the last 60 days\. This directory and its contents can be deleted, but this may result in slower connection initations since it will require that we go out and re\-fetch full identities for peers we're speaking to\. +.IP \(bu 2 +\fBnetworks\.d\fP (directory): +This caches network configurations and certificate information for networks you belong to\. ZeroTier scans this directory for \|\.conf files on startup to recall its networks, so "touch"ing an empty \|\.conf file in this directory is a way of pre\-configuring ZeroTier to join a specific network on startup without using the API\. If the config file is empty ZeroTIer will just fetch it from the network's controller\. + +.RE +.SH COPYRIGHT +.P +(c)2011\-2016 ZeroTier, Inc\. \-\- https://www\.zerotier\.com/ \-\- https://github\.com/zerotier +.SH SEE ALSO +.P +zerotier\-cli(1), zerotier\-idtool(1) + diff --git a/doc/zerotier-one.8.md b/doc/zerotier-one.8.md new file mode 100644 index 0000000..bd31d5c --- /dev/null +++ b/doc/zerotier-one.8.md @@ -0,0 +1,95 @@ +zerotier-one(8) -- ZeroTier virtual network endpoint service +============================================================ + +## SYNOPSIS + +`zerotier-one` [-switches] [working directory] + +## DESCRIPTION + +**zerotier-one** is the service/daemon responsible for connecting a Unix (Linux/BSD/OSX) system to one or more ZeroTier virtual networks and presenting those networks to the system as virtual network ports. You can think of it as a peer to peer VPN client. + +It's typically run by init systems like systemd (Linux) or launchd (Mac) rather than directly by the user, and it must be run as root unless you give it the `-U` switch and don't plan on actually joining networks (e.g. to run a network controller microservice only). + +The **zerotier-one** service keeps its state and other files in a working directory. If this directory is not specified at launch it defaults to "/var/lib/zerotier-one" on Linux, "/Library/Application Support/ZeroTier/One" on Mac, and "/var/db/zerotier-one" on FreeBSD and other similar BSDs. The working directory should persist. It shouldn't be automatically cleaned by system cleanup daemons or stored in a volatile location. Loss of its identity.secret file results in loss of this system's unique 10-digit ZeroTier address and key. + +Multiple instances of **zerotier-one** can be run on the same system as long as they are run with different primary ports (see switches) and a different working directory. But since a single service can join any number of networks, typically there's no point in doing this. + +The **zerotier-one** service is controlled via a JSON API available at 127.0.0.1: with the default primary port being 9993. Access to this API requires an authorization token normally found in the authtoken.secret file in the service's working directory. On some platforms access may be guarded by other measures such as socket peer UID/GID lookup if additional security options are enabled (this is not the default). + +The first time the service is started in a fresh working directory, it generates a ZeroTier identity. On slow systems this process can take ten seconds or more due to an anti-DDOS/anti-counterfeit proof of work function used by ZeroTier in address generation. This only happens once, and once generated the result is saved in identity.secret in the working directory. This file represents and defines/claims your ZeroTier address and associated ECC-256 key pair. + +## SWITCHES + + * `-h`: + Display help. + + * `-v`: + Display ZeroTier One version. + + * `-U`: + Skip privilege check and allow to be run by non-privileged user. This is typically used when **zerotier-one** is built with the network controller option included. In this case the ZeroTier service might only be acting as a network controller and might never actually join networks, in which case it does not require elevated system permissions. + + * `-p`: + Specify a different primary port. If this is not given the default is 9993. If zero is given a random port is chosen each time. + + * `-d`: + Fork and run as a daemon. + + * `-i`: + Invoke the **zerotier-idtool** personality, in which case the binary behaves like zerotier-idtool(1). This happens automatically if the name of the binary (or a symlink to it) is zerotier-idtool. + + * `-q`: + Invoke the **zerotier-cli** personality, in which case the binary behaves like zerotier-cli(1). This happens automatically if the name of the binary (or a symlink to it) is zerotier-cli. + +## EXAMPLES + +Run as daemon with OS default working directory and default port: + + $ sudo zerotier-one -d + +Run as daemon with a different working directory and port: + + $ sudo zerotier-one -d -p12345 /tmp/zerotier-working-directory-test + +## FILES + +These are found in the service's working directory. + + * `identity.public`: + The public portion of your ZeroTier identity, which is your 10-digit hex address and the associated public key. + + * `identity.secret`: + Your full ZeroTier identity including its private key. This file identifies the system on the network, which means you can move a ZeroTier address around by copying this file and you should back up this file if you want to save your system's static ZeroTier address. This file must be protected, since theft of its secret key will allow anyone to impersonate your device on any network and decrypt traffic. For network controllers this file is particularly sensitive since it constitutes the private key for a certificate authority for the controller's networks. + + * `authtoken.secret`: + The secret token used to authenticate requests to the service's local JSON API. If it does not exist it is generated from a secure random source on service start. To use, send it in the "X-ZT1-Auth" header with HTTP requests to 127.0.0.1:. + + * `devicemap`: + Remembers mappings of zt# interface numbers to ZeroTier networks so they'll persist across restarts. On some systems that support longer interface names that can encode the network ID (such as FreeBSD) this file may not be present. + + * `zerotier-one.pid`: + ZeroTier's PID. This file is deleted on normal shutdown. + + * `zerotier-one.port`: + ZeroTier's primary port, which is also where its JSON API is found at 127.0.0.1:. This file is created on startup and is read by zerotier-cli(1) to determine where it should find the control API. + + * `controller.db`: + If the ZeroTier One service is built with the network controller enabled, this file contains the controller's SQLite3 database. + + * `controller.db.backup`: + If the ZeroTier One service is built with the network controller enabled, it periodically backs up its controller.db database in this file (currently every 5 minutes if there have been changes). Since this file is not a currently in use SQLite3 database it's safer to back up without corruption. On new backups the file is rotated out rather than being rewritten in place. + + * `iddb.d/` (directory): + Caches the public identity of every peer ZeroTier has spoken with in the last 60 days. This directory and its contents can be deleted, but this may result in slower connection initations since it will require that we go out and re-fetch full identities for peers we're speaking to. + + * `networks.d` (directory): + This caches network configurations and certificate information for networks you belong to. ZeroTier scans this directory for .conf files on startup to recall its networks, so "touch"ing an empty .conf file in this directory is a way of pre-configuring ZeroTier to join a specific network on startup without using the API. If the config file is empty ZeroTIer will just fetch it from the network's controller. + +## COPYRIGHT + +(c)2011-2016 ZeroTier, Inc. -- https://www.zerotier.com/ -- https://github.com/zerotier + +## SEE ALSO + +zerotier-cli(1), zerotier-idtool(1) diff --git a/ext/README.md b/ext/README.md new file mode 100644 index 0000000..be9484c --- /dev/null +++ b/ext/README.md @@ -0,0 +1,10 @@ +Miscellaneous Stuff +====== + +This subfolder contains: + + * Bundled third party libraries that are compiled into the binary on platforms and Linux distributions where they are not available on the system. + + * Pre-compiled binaries for some platforms, such as pre-built and signed drivers for Mac and Windows. + + * Miscellaneous files used by installers and packages on various platform targets. diff --git a/ext/arm32-neon-salsa2012-asm/README.md b/ext/arm32-neon-salsa2012-asm/README.md new file mode 100644 index 0000000..54fc6f5 --- /dev/null +++ b/ext/arm32-neon-salsa2012-asm/README.md @@ -0,0 +1,6 @@ +ARM NEON (32-bit) ASM implementation of Salsa20/12 +====== + +This is from [supercop](http://bench.cr.yp.to/supercop.html) and was originally written by Daniel J. Bernstein. Code is in the public domain like the rest of Salsa20. It's much faster than the naive implementation. + +It's included automatically in 32-bit Linux ARM builds. It likely will not work on 64-bit ARM, so it'll need to be ported at least. That will unfortunately keep it out of mobile versions for now since those are all going 64-bit. diff --git a/ext/arm32-neon-salsa2012-asm/salsa2012.h b/ext/arm32-neon-salsa2012-asm/salsa2012.h new file mode 100644 index 0000000..95b247f --- /dev/null +++ b/ext/arm32-neon-salsa2012-asm/salsa2012.h @@ -0,0 +1,23 @@ +#ifndef ZT_SALSA2012_ARM32NEON_ASM +#define ZT_SALSA2012_ARM32NEON_ASM + +#if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) +#include +#include +#define zt_arm_has_neon() ((getauxval(AT_HWCAP) & HWCAP_NEON) != 0) +#else +#define zt_arm_has_neon() (true) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ciphertext buffer, message/NULL, length, nonce (8 bytes), key (32 bytes) +extern int zt_salsa2012_armneon3_xor(unsigned char *c,const unsigned char *m,unsigned long long len,const unsigned char *n,const unsigned char *k); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/arm32-neon-salsa2012-asm/salsa2012.s b/ext/arm32-neon-salsa2012-asm/salsa2012.s new file mode 100644 index 0000000..9e5989c --- /dev/null +++ b/ext/arm32-neon-salsa2012-asm/salsa2012.s @@ -0,0 +1,2231 @@ + +# qhasm: int32 input_0 + +# qhasm: int32 input_1 + +# qhasm: int32 input_2 + +# qhasm: int32 input_3 + +# qhasm: stack32 input_4 + +# qhasm: stack32 input_5 + +# qhasm: stack32 input_6 + +# qhasm: stack32 input_7 + +# qhasm: int32 caller_r4 + +# qhasm: int32 caller_r5 + +# qhasm: int32 caller_r6 + +# qhasm: int32 caller_r7 + +# qhasm: int32 caller_r8 + +# qhasm: int32 caller_r9 + +# qhasm: int32 caller_r10 + +# qhasm: int32 caller_r11 + +# qhasm: int32 caller_r14 + +# qhasm: reg128 caller_q4 + +# qhasm: reg128 caller_q5 + +# qhasm: reg128 caller_q6 + +# qhasm: reg128 caller_q7 + +# qhasm: startcode +.fpu neon +.text + +# qhasm: constant sigma: +.align 2 +sigma: + +# qhasm: const32 1634760805 +.word 1634760805 + +# qhasm: const32 857760878 +.word 857760878 + +# qhasm: const32 2036477234 +.word 2036477234 + +# qhasm: const32 1797285236 +.word 1797285236 + +# qhasm: int128 abab + +# qhasm: int128 diag0 + +# qhasm: int128 diag1 + +# qhasm: int128 diag2 + +# qhasm: int128 diag3 + +# qhasm: int128 a0 + +# qhasm: int128 a1 + +# qhasm: int128 a2 + +# qhasm: int128 a3 + +# qhasm: int128 b0 + +# qhasm: int128 b1 + +# qhasm: int128 b2 + +# qhasm: int128 b3 + +# qhasm: int128 next_diag0 + +# qhasm: int128 next_diag1 + +# qhasm: int128 next_diag2 + +# qhasm: int128 next_diag3 + +# qhasm: int128 next_a0 + +# qhasm: int128 next_a1 + +# qhasm: int128 next_a2 + +# qhasm: int128 next_a3 + +# qhasm: int128 next_b0 + +# qhasm: int128 next_b1 + +# qhasm: int128 next_b2 + +# qhasm: int128 next_b3 + +# qhasm: int128 x0x5x10x15 + +# qhasm: int128 x12x1x6x11 + +# qhasm: int128 x8x13x2x7 + +# qhasm: int128 x4x9x14x3 + +# qhasm: int128 x0x1x10x11 + +# qhasm: int128 x12x13x6x7 + +# qhasm: int128 x8x9x2x3 + +# qhasm: int128 x4x5x14x15 + +# qhasm: int128 x0x1x2x3 + +# qhasm: int128 x4x5x6x7 + +# qhasm: int128 x8x9x10x11 + +# qhasm: int128 x12x13x14x15 + +# qhasm: int128 m0m1m2m3 + +# qhasm: int128 m4m5m6m7 + +# qhasm: int128 m8m9m10m11 + +# qhasm: int128 m12m13m14m15 + +# qhasm: int128 start0 + +# qhasm: int128 start1 + +# qhasm: int128 start2 + +# qhasm: int128 start3 + +# qhasm: stack128 stack_start3 + +# qhasm: stack128 next_start2 + +# qhasm: stack128 next_start3 + +# qhasm: int128 k0k1k2k3 + +# qhasm: int128 k4k5k6k7 + +# qhasm: int128 k1n1k7k2 + +# qhasm: int128 n2n3n3n2 + +# qhasm: int128 k2k3k6k7 + +# qhasm: int128 nextblock + +# qhasm: stack128 stack_q4 + +# qhasm: stack128 stack_q5 + +# qhasm: stack128 stack_q6 + +# qhasm: stack128 stack_q7 + +# qhasm: stack32 stack_r4 + +# qhasm: stack128 k2k3k6k7_stack + +# qhasm: stack128 k1n1k7k2_stack + +# qhasm: stack512 tmp + +# qhasm: stack32 savec + +# qhasm: int32 i + +# qhasm: int32 ci + +# qhasm: int32 mi + +# qhasm: enter zt_salsa2012_armneon3_xor +.align 2 +.global _zt_salsa2012_armneon3_xor +.global zt_salsa2012_armneon3_xor +.type _zt_salsa2012_armneon3_xor STT_FUNC +.type zt_salsa2012_armneon3_xor STT_FUNC +_zt_salsa2012_armneon3_xor: +zt_salsa2012_armneon3_xor: +sub sp,sp,#256 + +# qhasm: new stack_q4 + +# qhasm: new stack_q5 + +# qhasm: new stack_q6 + +# qhasm: new stack_q7 + +# qhasm: stack_q4 bot = caller_q4 bot +# asm 1: vstr stack_r4=stack32#2 +# asm 2: str stack_r4=[sp,#68] +str r4,[sp,#68] + +# qhasm: int32 c + +# qhasm: c = input_0 +# asm 1: mov >c=int32#1,c=r0,m=int32#2,m=r1,mlenlow=int32#3,mlenlow=r2,mlenhigh=int32#4,mlenhigh=r3,n=int32#5,n=r4,k=int32#13,k=r12,k0k1k2k3=reg128#1%bot->k0k1k2k3=reg128#1%top},[k0k1k2k3=d0->k0k1k2k3=d1},[k4k5k6k7=reg128#2%bot->k4k5k6k7=reg128#2%top},[k4k5k6k7=d2->k4k5k6k7=d3},[i=int32#13,=sigma +# asm 2: ldr >i=r12,=sigma +ldr r12,=sigma + +# qhasm: start0 = mem128[i] +# asm 1: vld1.8 {>start0=reg128#3%bot->start0=reg128#3%top},[start0=d4->start0=d5},[start1=reg128#4,#0 +# asm 2: vmov.i64 >start1=q3,#0 +vmov.i64 q3,#0 + +# qhasm: start1 bot = mem64[n] +# asm 1: vld1.8 {k2k3k6k7=reg128#6,k2k3k6k7=q5,n2n3n3n2=reg128#1,#0 +# asm 2: vmov.i64 >n2n3n3n2=q0,#0 +vmov.i64 q0,#0 + +# qhasm: unsigneddiag0=reg128#8,diag0=q7,diag1=reg128#9,diag1=q8,start2=reg128#10,start2=q9,nextblock=reg128#11,#0xff +# asm 2: vmov.i64 >nextblock=q10,#0xff +vmov.i64 q10,#0xff + +# qhasm: 4x nextblock unsigned>>= 7 +# asm 1: vshr.u32 >nextblock=reg128#11,nextblock=q10,n2n3n3n2=reg128#1,n2n3n3n2=q0,n2n3n3n2=reg128#1,n2n3n3n2=q0,next_diag0=reg128#2,next_diag0=q1,next_diag1=reg128#5,next_diag1=q4,i=int32#5,=12 +# asm 2: ldr >i=r4,=12 +ldr r4,=12 + +# qhasm: mainloop2: +._mainloop2: + +# qhasm: 4x a0 = diag1 + diag0 +# asm 1: vadd.i32 >a0=reg128#11,a0=q10,next_a0=reg128#14,next_a0=q13,b0=reg128#15,b0=q14,next_b0=reg128#16,next_b0=q15,> 25 +# asm 1: vsri.i32 > 25 +# asm 1: vsri.i32 diag3=reg128#7,diag3=q6,next_diag3=reg128#11,next_diag3=q10,a1=reg128#13,a1=q12,next_a1=reg128#14,next_a1=q13,b1=reg128#15,b1=q14,next_b1=reg128#16,next_b1=q15,> 23 +# asm 1: vsri.i32 > 23 +# asm 1: vsri.i32 diag2=reg128#6,diag2=q5,next_diag2=reg128#12,next_diag2=q11,a2=reg128#13,a2=q12,diag3=reg128#7,diag3=q6,next_a2=reg128#14,next_a2=q13,b2=reg128#15,b2=q14,next_diag3=reg128#11,next_diag3=q10,next_b2=reg128#16,next_b2=q15,> 19 +# asm 1: vsri.i32 > 19 +# asm 1: vsri.i32 diag1=reg128#9,diag1=q8,next_diag1=reg128#5,next_diag1=q4,a3=reg128#13,a3=q12,next_a3=reg128#14,next_a3=q13,b3=reg128#15,b3=q14,next_b3=reg128#16,next_b3=q15,> 14 +# asm 1: vsri.i32 diag1=reg128#9,diag1=q8,> 14 +# asm 1: vsri.i32 diag0=reg128#8,diag0=q7,next_diag1=reg128#5,next_diag1=q4,next_diag0=reg128#2,next_diag0=q1,a0=reg128#13,a0=q12,next_a0=reg128#14,next_a0=q13,b0=reg128#15,b0=q14,next_b0=reg128#16,next_b0=q15,> 25 +# asm 1: vsri.i32 > 25 +# asm 1: vsri.i32 diag1=reg128#9,diag1=q8,next_diag1=reg128#5,next_diag1=q4,a1=reg128#13,a1=q12,next_a1=reg128#14,next_a1=q13,b1=reg128#15,b1=q14,next_b1=reg128#16,next_b1=q15,> 23 +# asm 1: vsri.i32 ? i -= 2 +# asm 1: subs > 23 +# asm 1: vsri.i32 diag2=reg128#6,diag2=q5,next_diag2=reg128#12,next_diag2=q11,a2=reg128#13,a2=q12,diag1=reg128#9,diag1=q8,next_a2=reg128#14,next_a2=q13,b2=reg128#15,b2=q14,next_diag1=reg128#5,next_diag1=q4,next_b2=reg128#16,next_b2=q15,> 19 +# asm 1: vsri.i32 > 19 +# asm 1: vsri.i32 diag3=reg128#7,diag3=q6,next_diag3=reg128#11,next_diag3=q10,a3=reg128#13,a3=q12,next_a3=reg128#14,next_a3=q13,b3=reg128#15,b3=q14,next_b3=reg128#16,next_b3=q15,> 14 +# asm 1: vsri.i32 diag3=reg128#7,diag3=q6,> 14 +# asm 1: vsri.i32 diag0=reg128#8,diag0=q7,next_diag3=reg128#13,next_diag3=q12,next_diag0=reg128#2,next_diag0=q1, +bhi ._mainloop2 + +# qhasm: 2x abab = 0xffffffff +# asm 1: vmov.i64 >abab=reg128#11,#0xffffffff +# asm 2: vmov.i64 >abab=q10,#0xffffffff +vmov.i64 q10,#0xffffffff + +# qhasm: new x4x9x14x3 + +# qhasm: x4x9x14x3 bot = stack_start3 bot +# asm 1: vldr x0x5x10x15=reg128#8,x0x5x10x15=q7,x12x1x6x11=reg128#9,x12x1x6x11=q8,x8x13x2x7=reg128#6,x8x13x2x7=q5,x4x9x14x3=reg128#7,x4x9x14x3=q6,x0x1x10x11=reg128#10,x0x1x10x11=q9,x12x13x6x7=reg128#14,x12x13x6x7=q13,x8x9x2x3=reg128#15,x8x9x2x3=q14,x4x5x14x15=reg128#16,x4x5x14x15=q15,x0x1x2x3=reg128#6,x0x1x2x3=q5,x4x5x6x7=reg128#7,x4x5x6x7=q6,x8x9x10x11=reg128#8,x8x9x10x11=q7,x12x13x14x15=reg128#9,x12x13x14x15=q8,m0m1m2m3=reg128#10%bot->m0m1m2m3=reg128#10%top},[m0m1m2m3=d18->m0m1m2m3=d19},[m4m5m6m7=reg128#14%bot->m4m5m6m7=reg128#14%top},[m4m5m6m7=d26->m4m5m6m7=d27},[m8m9m10m11=reg128#15%bot->m8m9m10m11=reg128#15%top},[m8m9m10m11=d28->m8m9m10m11=d29},[m12m13m14m15=reg128#16%bot->m12m13m14m15=reg128#16%top},[m12m13m14m15=d30->m12m13m14m15=d31},[x0x1x2x3=reg128#6,x0x1x2x3=q5,x4x5x6x7=reg128#7,x4x5x6x7=q6,x8x9x10x11=reg128#8,x8x9x10x11=q7,x12x13x14x15=reg128#9,x12x13x14x15=q8,x0x5x10x15=reg128#2,x0x5x10x15=q1,x12x1x6x11=reg128#5,x12x1x6x11=q4,x8x13x2x7=reg128#6,x8x13x2x7=q5,x4x9x14x3=reg128#7,x4x9x14x3=q6,x0x1x10x11=reg128#8,x0x1x10x11=q7,x12x13x6x7=reg128#9,x12x13x6x7=q8,x8x9x2x3=reg128#10,x8x9x2x3=q9,x4x5x14x15=reg128#12,x4x5x14x15=q11,x0x1x2x3=reg128#2,x0x1x2x3=q1,x4x5x6x7=reg128#5,x4x5x6x7=q4,x8x9x10x11=reg128#6,x8x9x10x11=q5,x12x13x14x15=reg128#7,x12x13x14x15=q6,m0m1m2m3=reg128#8%bot->m0m1m2m3=reg128#8%top},[m0m1m2m3=d14->m0m1m2m3=d15},[m4m5m6m7=reg128#9%bot->m4m5m6m7=reg128#9%top},[m4m5m6m7=d16->m4m5m6m7=d17},[m8m9m10m11=reg128#10%bot->m8m9m10m11=reg128#10%top},[m8m9m10m11=d18->m8m9m10m11=d19},[m12m13m14m15=reg128#11%bot->m12m13m14m15=reg128#11%top},[m12m13m14m15=d20->m12m13m14m15=d21},[x0x1x2x3=reg128#2,x0x1x2x3=q1,x4x5x6x7=reg128#5,x4x5x6x7=q4,x8x9x10x11=reg128#6,x8x9x10x11=q5,x12x13x14x15=reg128#7,x12x13x14x15=q6,? mlenhigh - 0 +# asm 1: cmp +bhi ._mlenatleast128 + +# qhasm: =? mlenlow - 0 +# asm 1: cmp savec=stack32#1 +# asm 2: str savec=[sp,#64] +str r0,[sp,#64] + +# qhasm: c = &tmp +# asm 1: lea >c=int32#1,c=r0,i=int32#4,=0 +# asm 2: ldr >i=r3,=0 +ldr r3,=0 + +# qhasm: mcopy: +._mcopy: + +# qhasm: mi = mem8[m + 0] +# asm 1: ldrb >mi=int32#5,[mi=r4,[mi=int32#2,=0 +# asm 2: ldr >mi=r1,=0 +ldr r1,=0 + +# qhasm: pad: +._pad: + +# qhasm: mem8[c + 0] = mi +# asm 1: strb m=int32#2,m=r1,diag0=reg128#2,diag0=q1,diag1=reg128#5,diag1=q4,diag2=reg128#8,diag2=q7,diag3=reg128#9,diag3=q8,nextblock=reg128#10,#0xff +# asm 2: vmov.i64 >nextblock=q9,#0xff +vmov.i64 q9,#0xff + +# qhasm: 4x nextblock unsigned>>= 7 +# asm 1: vshr.u32 >nextblock=reg128#10,nextblock=q9,n2n3n3n2=reg128#1,n2n3n3n2=q0,i=int32#4,=12 +# asm 2: ldr >i=r3,=12 +ldr r3,=12 + +# qhasm: mainloop1: +._mainloop1: + +# qhasm: 4x a0 = diag1 + diag0 +# asm 1: vadd.i32 >a0=reg128#10,a0=q9,b0=reg128#11,b0=q10,> 25 +# asm 1: vsri.i32 diag3=reg128#9,diag3=q8,a1=reg128#10,a1=q9,b1=reg128#11,b1=q10,> 23 +# asm 1: vsri.i32 diag2=reg128#8,diag2=q7,a2=reg128#10,a2=q9,diag3=reg128#9,diag3=q8,b2=reg128#11,b2=q10,> 19 +# asm 1: vsri.i32 diag1=reg128#5,diag1=q4,a3=reg128#10,a3=q9,b3=reg128#11,b3=q10,> 14 +# asm 1: vsri.i32 diag1=reg128#5,diag1=q4,diag0=reg128#2,diag0=q1,a0=reg128#10,a0=q9,b0=reg128#11,b0=q10,> 25 +# asm 1: vsri.i32 diag1=reg128#5,diag1=q4,a1=reg128#10,a1=q9,b1=reg128#11,b1=q10,> 23 +# asm 1: vsri.i32 ? i -= 2 +# asm 1: subs diag2=reg128#8,diag2=q7,a2=reg128#10,a2=q9,diag1=reg128#5,diag1=q4,b2=reg128#11,b2=q10,> 19 +# asm 1: vsri.i32 diag3=reg128#9,diag3=q8,a3=reg128#10,a3=q9,b3=reg128#11,b3=q10,> 14 +# asm 1: vsri.i32 diag3=reg128#9,diag3=q8,diag0=reg128#2,diag0=q1, +bhi ._mainloop1 + +# qhasm: 2x abab = 0xffffffff +# asm 1: vmov.i64 >abab=reg128#10,#0xffffffff +# asm 2: vmov.i64 >abab=q9,#0xffffffff +vmov.i64 q9,#0xffffffff + +# qhasm: 4x x0x5x10x15 = diag0 + start0 +# asm 1: vadd.i32 >x0x5x10x15=reg128#2,x0x5x10x15=q1,x12x1x6x11=reg128#5,x12x1x6x11=q4,x8x13x2x7=reg128#6,x8x13x2x7=q5,x4x9x14x3=reg128#7,x4x9x14x3=q6,x0x1x10x11=reg128#8,x0x1x10x11=q7,x12x13x6x7=reg128#9,x12x13x6x7=q8,x8x9x2x3=reg128#11,x8x9x2x3=q10,x4x5x14x15=reg128#12,x4x5x14x15=q11,x0x1x2x3=reg128#2,x0x1x2x3=q1,x4x5x6x7=reg128#5,x4x5x6x7=q4,x8x9x10x11=reg128#6,x8x9x10x11=q5,x12x13x14x15=reg128#7,x12x13x14x15=q6,m0m1m2m3=reg128#8%bot->m0m1m2m3=reg128#8%top},[m0m1m2m3=d14->m0m1m2m3=d15},[m4m5m6m7=reg128#9%bot->m4m5m6m7=reg128#9%top},[m4m5m6m7=d16->m4m5m6m7=d17},[m8m9m10m11=reg128#10%bot->m8m9m10m11=reg128#10%top},[m8m9m10m11=d18->m8m9m10m11=d19},[m12m13m14m15=reg128#11%bot->m12m13m14m15=reg128#11%top},[m12m13m14m15=d20->m12m13m14m15=d21},[x0x1x2x3=reg128#2,x0x1x2x3=q1,x4x5x6x7=reg128#5,x4x5x6x7=q4,x8x9x10x11=reg128#6,x8x9x10x11=q5,x12x13x14x15=reg128#7,x12x13x14x15=q6,i=int32#4,=0 +# asm 2: ldr >i=r3,=0 +ldr r3,=0 + +# qhasm: m = c - 64 +# asm 1: sub >m=int32#2,m=r1,c=int32#1,c=r0,ci=int32#5,[ci=r4,[? mlenlow -= 64 +# asm 1: subs +bhi ._mlenatleast1 + +# qhasm: done: +._done: + +# qhasm: new caller_r4 + +# qhasm: caller_r4 = stack_r4 +# asm 1: ldr >caller_r4=int32#5,caller_r4=r4,result=int32#1,=0 +# asm 2: ldr >result=r0,=0 +ldr r0,=0 + +# qhasm: return result +add sp,sp,#256 +bx lr diff --git a/ext/bin/tap-mac/tap.kext/Contents/Info.plist b/ext/bin/tap-mac/tap.kext/Contents/Info.plist new file mode 100644 index 0000000..c20eefa --- /dev/null +++ b/ext/bin/tap-mac/tap.kext/Contents/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + tap + CFBundleIdentifier + com.zerotier.tap + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + tap + CFBundlePackageType + KEXT + CFBundleShortVersionString + 20150118 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + OSBundleLibraries + + com.apple.kpi.mach + 8.0 + com.apple.kpi.bsd + 8.0 + com.apple.kpi.libkern + 8.0 + com.apple.kpi.unsupported + 8.0 + + + + diff --git a/ext/bin/tap-mac/tap.kext/Contents/MacOS/tap b/ext/bin/tap-mac/tap.kext/Contents/MacOS/tap new file mode 100755 index 0000000..48bf962 Binary files /dev/null and b/ext/bin/tap-mac/tap.kext/Contents/MacOS/tap differ diff --git a/ext/bin/tap-mac/tap.kext/Contents/_CodeSignature/CodeResources b/ext/bin/tap-mac/tap.kext/Contents/_CodeSignature/CodeResources new file mode 100644 index 0000000..0710b40 --- /dev/null +++ b/ext/bin/tap-mac/tap.kext/Contents/_CodeSignature/CodeResources @@ -0,0 +1,105 @@ + + + + + files + + files2 + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/ext/bin/tap-windows-ndis6/x64/ZeroTierOne_NDIS6_x64.msi b/ext/bin/tap-windows-ndis6/x64/ZeroTierOne_NDIS6_x64.msi new file mode 100644 index 0000000..4cfb7d7 Binary files /dev/null and b/ext/bin/tap-windows-ndis6/x64/ZeroTierOne_NDIS6_x64.msi differ diff --git a/ext/bin/tap-windows-ndis6/x64/zttap300.cat b/ext/bin/tap-windows-ndis6/x64/zttap300.cat new file mode 100644 index 0000000..8b9114c Binary files /dev/null and b/ext/bin/tap-windows-ndis6/x64/zttap300.cat differ diff --git a/ext/bin/tap-windows-ndis6/x64/zttap300.inf b/ext/bin/tap-windows-ndis6/x64/zttap300.inf new file mode 100644 index 0000000..453797b --- /dev/null +++ b/ext/bin/tap-windows-ndis6/x64/zttap300.inf @@ -0,0 +1,143 @@ +; +; ZeroTier One Virtual Network Port NDIS6 Driver +; +; Based on the OpenVPN tap-windows6 driver version 9.21.1 git +; commit 48f027cfca52b16b5fd23d82e6016ed8a91fc4d3. +; See: https://github.com/OpenVPN/tap-windows6 +; +; Modified by ZeroTier, Inc. - https://www.zerotier.com/ +; +; (1) Comment out 'tun' functionality and related features such as DHCP +; emulation, since we don't use any of that. Just want straight 'tap'. +; (2) Added custom IOCTL to enumerate L2 multicast memberships. +; (3) Increase maximum number of multicast memberships to 128. +; (4) Set default and max device MTU to 2800. +; (5) Rename/rebrand driver as ZeroTier network port driver. +; +; Original copyright below. Modifications released under GPLv2 as well. +; +; **************************************************************************** +; * Copyright (C) 2002-2014 OpenVPN Technologies, Inc. * +; * This program is free software; you can redistribute it and/or modify * +; * it under the terms of the GNU General Public License version 2 * +; * as published by the Free Software Foundation. * +; **************************************************************************** +; + +[Version] +Signature = "$Windows NT$" +CatalogFile = zttap300.cat +ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} +Provider = %Provider% +Class = Net +DriverVer=08/13/2015,6.2.9200.20557 + +[Strings] +DeviceDescription = "ZeroTier One Virtual Port" +Provider = "ZeroTier Networks LLC" ; We're ZeroTier, Inc. now but kernel mode certs are $300+ so fuqdat. + +; To build for x86, take NTamd64 off this and off the named section manually, build, then put it back! +[Manufacturer] +%Provider%=zttap300,NTamd64 + +[zttap300] +%DeviceDescription% = zttap300.ndi, root\zttap300 ; Root enumerated +%DeviceDescription% = zttap300.ndi, zttap300 ; Legacy + +[zttap300.NTamd64] +%DeviceDescription% = zttap300.ndi, root\zttap300 ; Root enumerated +%DeviceDescription% = zttap300.ndi, zttap300 ; Legacy + +;----------------- Characteristics ------------ +; NCF_PHYSICAL = 0x04 +; NCF_VIRTUAL = 0x01 +; NCF_SOFTWARE_ENUMERATED = 0x02 +; NCF_HIDDEN = 0x08 +; NCF_NO_SERVICE = 0x10 +; NCF_HAS_UI = 0x80 +;----------------- Characteristics ------------ +[zttap300.ndi] +CopyFiles = zttap300.driver, zttap300.files +AddReg = zttap300.reg +AddReg = zttap300.params.reg +Characteristics = 0x81 +*IfType = 0x6 ; IF_TYPE_ETHERNET_CSMACD +*MediaType = 0x0 ; NdisMedium802_3 +*PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 + +[zttap300.ndi.Services] +AddService = zttap300, 2, zttap300.service + +[zttap300.reg] +HKR, Ndi, Service, 0, "zttap300" +HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" ; yes, 'ndis5' is correct... yup, Windows. +HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" +HKR, , Manufacturer, 0, "%Provider%" +HKR, , ProductName, 0, "%DeviceDescription%" + +[zttap300.params.reg] +HKR, Ndi\params\MTU, ParamDesc, 0, "MTU" +HKR, Ndi\params\MTU, Type, 0, "int" +HKR, Ndi\params\MTU, Default, 0, "2800" +HKR, Ndi\params\MTU, Optional, 0, "0" +HKR, Ndi\params\MTU, Min, 0, "100" +HKR, Ndi\params\MTU, Max, 0, "2800" +HKR, Ndi\params\MTU, Step, 0, "1" +HKR, Ndi\params\MediaStatus, ParamDesc, 0, "Media Status" +HKR, Ndi\params\MediaStatus, Type, 0, "enum" +HKR, Ndi\params\MediaStatus, Default, 0, "0" +HKR, Ndi\params\MediaStatus, Optional, 0, "0" +HKR, Ndi\params\MediaStatus\enum, "0", 0, "Application Controlled" +HKR, Ndi\params\MediaStatus\enum, "1", 0, "Always Connected" +HKR, Ndi\params\MAC, ParamDesc, 0, "MAC Address" +HKR, Ndi\params\MAC, Type, 0, "edit" +HKR, Ndi\params\MAC, Optional, 0, "1" +HKR, Ndi\params\AllowNonAdmin, ParamDesc, 0, "Non-Admin Access" +HKR, Ndi\params\AllowNonAdmin, Type, 0, "enum" +HKR, Ndi\params\AllowNonAdmin, Default, 0, "0" +HKR, Ndi\params\AllowNonAdmin, Optional, 0, "0" +HKR, Ndi\params\AllowNonAdmin\enum, "0", 0, "Not Allowed" +HKR, Ndi\params\AllowNonAdmin\enum, "1", 0, "Allowed" + +;---------- Service Type ------------- +; SERVICE_KERNEL_DRIVER = 0x01 +; SERVICE_WIN32_OWN_PROCESS = 0x10 +;---------- Service Type ------------- + +;---------- Start Mode --------------- +; SERVICE_BOOT_START = 0x0 +; SERVICE_SYSTEM_START = 0x1 +; SERVICE_AUTO_START = 0x2 +; SERVICE_DEMAND_START = 0x3 +; SERVICE_DISABLED = 0x4 +;---------- Start Mode --------------- + +[zttap300.service] +DisplayName = %DeviceDescription% +ServiceType = 1 +StartType = 3 +ErrorControl = 1 +LoadOrderGroup = NDIS +ServiceBinary = %12%\zttap300.sys + +;----------------- Copy Flags ------------ +; COPYFLG_NOSKIP = 0x02 +; COPYFLG_NOVERSIONCHECK = 0x04 +;----------------- Copy Flags ------------ + +[SourceDisksNames] +1 = %DeviceDescription%, zttap300.sys + +[SourceDisksFiles] +zttap300.sys = 1 + +[DestinationDirs] +zttap300.files = 11 +zttap300.driver = 12 + +[zttap300.files] +; + +[zttap300.driver] +zttap300.sys,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK + diff --git a/ext/bin/tap-windows-ndis6/x64/zttap300.sys b/ext/bin/tap-windows-ndis6/x64/zttap300.sys new file mode 100644 index 0000000..3d846a5 Binary files /dev/null and b/ext/bin/tap-windows-ndis6/x64/zttap300.sys differ diff --git a/ext/bin/tap-windows-ndis6/x86/ZeroTierOne_NDIS6_x86.msi b/ext/bin/tap-windows-ndis6/x86/ZeroTierOne_NDIS6_x86.msi new file mode 100644 index 0000000..1b9aec4 Binary files /dev/null and b/ext/bin/tap-windows-ndis6/x86/ZeroTierOne_NDIS6_x86.msi differ diff --git a/ext/bin/tap-windows-ndis6/x86/zttap300.cat b/ext/bin/tap-windows-ndis6/x86/zttap300.cat new file mode 100644 index 0000000..44347f5 Binary files /dev/null and b/ext/bin/tap-windows-ndis6/x86/zttap300.cat differ diff --git a/ext/bin/tap-windows-ndis6/x86/zttap300.inf b/ext/bin/tap-windows-ndis6/x86/zttap300.inf new file mode 100644 index 0000000..453797b --- /dev/null +++ b/ext/bin/tap-windows-ndis6/x86/zttap300.inf @@ -0,0 +1,143 @@ +; +; ZeroTier One Virtual Network Port NDIS6 Driver +; +; Based on the OpenVPN tap-windows6 driver version 9.21.1 git +; commit 48f027cfca52b16b5fd23d82e6016ed8a91fc4d3. +; See: https://github.com/OpenVPN/tap-windows6 +; +; Modified by ZeroTier, Inc. - https://www.zerotier.com/ +; +; (1) Comment out 'tun' functionality and related features such as DHCP +; emulation, since we don't use any of that. Just want straight 'tap'. +; (2) Added custom IOCTL to enumerate L2 multicast memberships. +; (3) Increase maximum number of multicast memberships to 128. +; (4) Set default and max device MTU to 2800. +; (5) Rename/rebrand driver as ZeroTier network port driver. +; +; Original copyright below. Modifications released under GPLv2 as well. +; +; **************************************************************************** +; * Copyright (C) 2002-2014 OpenVPN Technologies, Inc. * +; * This program is free software; you can redistribute it and/or modify * +; * it under the terms of the GNU General Public License version 2 * +; * as published by the Free Software Foundation. * +; **************************************************************************** +; + +[Version] +Signature = "$Windows NT$" +CatalogFile = zttap300.cat +ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} +Provider = %Provider% +Class = Net +DriverVer=08/13/2015,6.2.9200.20557 + +[Strings] +DeviceDescription = "ZeroTier One Virtual Port" +Provider = "ZeroTier Networks LLC" ; We're ZeroTier, Inc. now but kernel mode certs are $300+ so fuqdat. + +; To build for x86, take NTamd64 off this and off the named section manually, build, then put it back! +[Manufacturer] +%Provider%=zttap300,NTamd64 + +[zttap300] +%DeviceDescription% = zttap300.ndi, root\zttap300 ; Root enumerated +%DeviceDescription% = zttap300.ndi, zttap300 ; Legacy + +[zttap300.NTamd64] +%DeviceDescription% = zttap300.ndi, root\zttap300 ; Root enumerated +%DeviceDescription% = zttap300.ndi, zttap300 ; Legacy + +;----------------- Characteristics ------------ +; NCF_PHYSICAL = 0x04 +; NCF_VIRTUAL = 0x01 +; NCF_SOFTWARE_ENUMERATED = 0x02 +; NCF_HIDDEN = 0x08 +; NCF_NO_SERVICE = 0x10 +; NCF_HAS_UI = 0x80 +;----------------- Characteristics ------------ +[zttap300.ndi] +CopyFiles = zttap300.driver, zttap300.files +AddReg = zttap300.reg +AddReg = zttap300.params.reg +Characteristics = 0x81 +*IfType = 0x6 ; IF_TYPE_ETHERNET_CSMACD +*MediaType = 0x0 ; NdisMedium802_3 +*PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 + +[zttap300.ndi.Services] +AddService = zttap300, 2, zttap300.service + +[zttap300.reg] +HKR, Ndi, Service, 0, "zttap300" +HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" ; yes, 'ndis5' is correct... yup, Windows. +HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" +HKR, , Manufacturer, 0, "%Provider%" +HKR, , ProductName, 0, "%DeviceDescription%" + +[zttap300.params.reg] +HKR, Ndi\params\MTU, ParamDesc, 0, "MTU" +HKR, Ndi\params\MTU, Type, 0, "int" +HKR, Ndi\params\MTU, Default, 0, "2800" +HKR, Ndi\params\MTU, Optional, 0, "0" +HKR, Ndi\params\MTU, Min, 0, "100" +HKR, Ndi\params\MTU, Max, 0, "2800" +HKR, Ndi\params\MTU, Step, 0, "1" +HKR, Ndi\params\MediaStatus, ParamDesc, 0, "Media Status" +HKR, Ndi\params\MediaStatus, Type, 0, "enum" +HKR, Ndi\params\MediaStatus, Default, 0, "0" +HKR, Ndi\params\MediaStatus, Optional, 0, "0" +HKR, Ndi\params\MediaStatus\enum, "0", 0, "Application Controlled" +HKR, Ndi\params\MediaStatus\enum, "1", 0, "Always Connected" +HKR, Ndi\params\MAC, ParamDesc, 0, "MAC Address" +HKR, Ndi\params\MAC, Type, 0, "edit" +HKR, Ndi\params\MAC, Optional, 0, "1" +HKR, Ndi\params\AllowNonAdmin, ParamDesc, 0, "Non-Admin Access" +HKR, Ndi\params\AllowNonAdmin, Type, 0, "enum" +HKR, Ndi\params\AllowNonAdmin, Default, 0, "0" +HKR, Ndi\params\AllowNonAdmin, Optional, 0, "0" +HKR, Ndi\params\AllowNonAdmin\enum, "0", 0, "Not Allowed" +HKR, Ndi\params\AllowNonAdmin\enum, "1", 0, "Allowed" + +;---------- Service Type ------------- +; SERVICE_KERNEL_DRIVER = 0x01 +; SERVICE_WIN32_OWN_PROCESS = 0x10 +;---------- Service Type ------------- + +;---------- Start Mode --------------- +; SERVICE_BOOT_START = 0x0 +; SERVICE_SYSTEM_START = 0x1 +; SERVICE_AUTO_START = 0x2 +; SERVICE_DEMAND_START = 0x3 +; SERVICE_DISABLED = 0x4 +;---------- Start Mode --------------- + +[zttap300.service] +DisplayName = %DeviceDescription% +ServiceType = 1 +StartType = 3 +ErrorControl = 1 +LoadOrderGroup = NDIS +ServiceBinary = %12%\zttap300.sys + +;----------------- Copy Flags ------------ +; COPYFLG_NOSKIP = 0x02 +; COPYFLG_NOVERSIONCHECK = 0x04 +;----------------- Copy Flags ------------ + +[SourceDisksNames] +1 = %DeviceDescription%, zttap300.sys + +[SourceDisksFiles] +zttap300.sys = 1 + +[DestinationDirs] +zttap300.files = 11 +zttap300.driver = 12 + +[zttap300.files] +; + +[zttap300.driver] +zttap300.sys,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK + diff --git a/ext/bin/tap-windows-ndis6/x86/zttap300.sys b/ext/bin/tap-windows-ndis6/x86/zttap300.sys new file mode 100644 index 0000000..664398e Binary files /dev/null and b/ext/bin/tap-windows-ndis6/x86/zttap300.sys differ diff --git a/ext/http-parser/AUTHORS b/ext/http-parser/AUTHORS new file mode 100644 index 0000000..5323b68 --- /dev/null +++ b/ext/http-parser/AUTHORS @@ -0,0 +1,68 @@ +# Authors ordered by first contribution. +Ryan Dahl +Jeremy Hinegardner +Sergey Shepelev +Joe Damato +tomika +Phoenix Sol +Cliff Frey +Ewen Cheslack-Postava +Santiago Gala +Tim Becker +Jeff Terrace +Ben Noordhuis +Nathan Rajlich +Mark Nottingham +Aman Gupta +Tim Becker +Sean Cunningham +Peter Griess +Salman Haq +Cliff Frey +Jon Kolb +Fouad Mardini +Paul Querna +Felix Geisendörfer +koichik +Andre Caron +Ivo Raisr +James McLaughlin +David Gwynne +Thomas LE ROUX +Randy Rizun +Andre Louis Caron +Simon Zimmermann +Erik Dubbelboer +Martell Malone +Bertrand Paquet +BogDan Vatra +Peter Faiman +Corey Richardson +Tóth Tamás +Cam Swords +Chris Dickinson +Uli Köhler +Charlie Somerville +Patrik Stutz +Fedor Indutny +runner +Alexis Campailla +David Wragg +Vinnie Falco +Alex Butum +Rex Feng +Alex Kocharin +Mark Koopman +Helge Heß +Alexis La Goutte +George Miroshnykov +Maciej Małecki +Marc O'Morain +Jeff Pinner +Timothy J Fontaine +Akagi201 +Romain Giraud +Jay Satiro +Arne Steen +Kjell Schubert +Olivier Mengué diff --git a/ext/http-parser/LICENSE-MIT b/ext/http-parser/LICENSE-MIT new file mode 100644 index 0000000..58010b3 --- /dev/null +++ b/ext/http-parser/LICENSE-MIT @@ -0,0 +1,23 @@ +http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright +Igor Sysoev. + +Additional changes are licensed under the same terms as NGINX and +copyright Joyent, Inc. and other Node contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/ext/http-parser/README.md b/ext/http-parser/README.md new file mode 100644 index 0000000..439b309 --- /dev/null +++ b/ext/http-parser/README.md @@ -0,0 +1,246 @@ +HTTP Parser +=========== + +[![Build Status](https://api.travis-ci.org/nodejs/http-parser.svg?branch=master)](https://travis-ci.org/nodejs/http-parser) + +This is a parser for HTTP messages written in C. It parses both requests and +responses. The parser is designed to be used in performance HTTP +applications. It does not make any syscalls nor allocations, it does not +buffer data, it can be interrupted at anytime. Depending on your +architecture, it only requires about 40 bytes of data per message +stream (in a web server that is per connection). + +Features: + + * No dependencies + * Handles persistent streams (keep-alive). + * Decodes chunked encoding. + * Upgrade support + * Defends against buffer overflow attacks. + +The parser extracts the following information from HTTP messages: + + * Header fields and values + * Content-Length + * Request method + * Response status code + * Transfer-Encoding + * HTTP version + * Request URL + * Message body + + +Usage +----- + +One `http_parser` object is used per TCP connection. Initialize the struct +using `http_parser_init()` and set the callbacks. That might look something +like this for a request parser: +```c +http_parser_settings settings; +settings.on_url = my_url_callback; +settings.on_header_field = my_header_field_callback; +/* ... */ + +http_parser *parser = malloc(sizeof(http_parser)); +http_parser_init(parser, HTTP_REQUEST); +parser->data = my_socket; +``` + +When data is received on the socket execute the parser and check for errors. + +```c +size_t len = 80*1024, nparsed; +char buf[len]; +ssize_t recved; + +recved = recv(fd, buf, len, 0); + +if (recved < 0) { + /* Handle error. */ +} + +/* Start up / continue the parser. + * Note we pass recved==0 to signal that EOF has been received. + */ +nparsed = http_parser_execute(parser, &settings, buf, recved); + +if (parser->upgrade) { + /* handle new protocol */ +} else if (nparsed != recved) { + /* Handle error. Usually just close the connection. */ +} +``` + +HTTP needs to know where the end of the stream is. For example, sometimes +servers send responses without Content-Length and expect the client to +consume input (for the body) until EOF. To tell http_parser about EOF, give +`0` as the fourth parameter to `http_parser_execute()`. Callbacks and errors +can still be encountered during an EOF, so one must still be prepared +to receive them. + +Scalar valued message information such as `status_code`, `method`, and the +HTTP version are stored in the parser structure. This data is only +temporally stored in `http_parser` and gets reset on each new message. If +this information is needed later, copy it out of the structure during the +`headers_complete` callback. + +The parser decodes the transfer-encoding for both requests and responses +transparently. That is, a chunked encoding is decoded before being sent to +the on_body callback. + + +The Special Problem of Upgrade +------------------------------ + +HTTP supports upgrading the connection to a different protocol. An +increasingly common example of this is the WebSocket protocol which sends +a request like + + GET /demo HTTP/1.1 + Upgrade: WebSocket + Connection: Upgrade + Host: example.com + Origin: http://example.com + WebSocket-Protocol: sample + +followed by non-HTTP data. + +(See [RFC6455](https://tools.ietf.org/html/rfc6455) for more information the +WebSocket protocol.) + +To support this, the parser will treat this as a normal HTTP message without a +body, issuing both on_headers_complete and on_message_complete callbacks. However +http_parser_execute() will stop parsing at the end of the headers and return. + +The user is expected to check if `parser->upgrade` has been set to 1 after +`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied +offset by the return value of `http_parser_execute()`. + + +Callbacks +--------- + +During the `http_parser_execute()` call, the callbacks set in +`http_parser_settings` will be executed. The parser maintains state and +never looks behind, so buffering the data is not necessary. If you need to +save certain data for later usage, you can do that from the callbacks. + +There are two types of callbacks: + +* notification `typedef int (*http_cb) (http_parser*);` + Callbacks: on_message_begin, on_headers_complete, on_message_complete. +* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);` + Callbacks: (requests only) on_url, + (common) on_header_field, on_header_value, on_body; + +Callbacks must return 0 on success. Returning a non-zero value indicates +error to the parser, making it exit immediately. + +For cases where it is necessary to pass local information to/from a callback, +the `http_parser` object's `data` field can be used. +An example of such a case is when using threads to handle a socket connection, +parse a request, and then give a response over that socket. By instantiation +of a thread-local struct containing relevant data (e.g. accepted socket, +allocated memory for callbacks to write into, etc), a parser's callbacks are +able to communicate data between the scope of the thread and the scope of the +callback in a threadsafe manner. This allows http-parser to be used in +multi-threaded contexts. + +Example: +```c + typedef struct { + socket_t sock; + void* buffer; + int buf_len; + } custom_data_t; + + +int my_url_callback(http_parser* parser, const char *at, size_t length) { + /* access to thread local custom_data_t struct. + Use this access save parsed data for later use into thread local + buffer, or communicate over socket + */ + parser->data; + ... + return 0; +} + +... + +void http_parser_thread(socket_t sock) { + int nparsed = 0; + /* allocate memory for user data */ + custom_data_t *my_data = malloc(sizeof(custom_data_t)); + + /* some information for use by callbacks. + * achieves thread -> callback information flow */ + my_data->sock = sock; + + /* instantiate a thread-local parser */ + http_parser *parser = malloc(sizeof(http_parser)); + http_parser_init(parser, HTTP_REQUEST); /* initialise parser */ + /* this custom data reference is accessible through the reference to the + parser supplied to callback functions */ + parser->data = my_data; + + http_parser_settings settings; /* set up callbacks */ + settings.on_url = my_url_callback; + + /* execute parser */ + nparsed = http_parser_execute(parser, &settings, buf, recved); + + ... + /* parsed information copied from callback. + can now perform action on data copied into thread-local memory from callbacks. + achieves callback -> thread information flow */ + my_data->buffer; + ... +} + +``` + +In case you parse HTTP message in chunks (i.e. `read()` request line +from socket, parse, read half headers, parse, etc) your data callbacks +may be called more than once. Http-parser guarantees that data pointer is only +valid for the lifetime of callback. You can also `read()` into a heap allocated +buffer to avoid copying memory around if this fits your application. + +Reading headers may be a tricky task if you read/parse headers partially. +Basically, you need to remember whether last header callback was field or value +and apply the following logic: + + (on_header_field and on_header_value shortened to on_h_*) + ------------------------ ------------ -------------------------------------------- + | State (prev. callback) | Callback | Description/action | + ------------------------ ------------ -------------------------------------------- + | nothing (first call) | on_h_field | Allocate new buffer and copy callback data | + | | | into it | + ------------------------ ------------ -------------------------------------------- + | value | on_h_field | New header started. | + | | | Copy current name,value buffers to headers | + | | | list and allocate new buffer for new name | + ------------------------ ------------ -------------------------------------------- + | field | on_h_field | Previous name continues. Reallocate name | + | | | buffer and append callback data to it | + ------------------------ ------------ -------------------------------------------- + | field | on_h_value | Value for current header started. Allocate | + | | | new buffer and copy callback data to it | + ------------------------ ------------ -------------------------------------------- + | value | on_h_value | Value continues. Reallocate value buffer | + | | | and append callback data to it | + ------------------------ ------------ -------------------------------------------- + + +Parsing URLs +------------ + +A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`. +Users of this library may wish to use it to parse URLs constructed from +consecutive `on_url` callbacks. + +See examples of reading in headers: + +* [partial example](http://gist.github.com/155877) in C +* [from http-parser tests](http://github.com/joyent/http-parser/blob/37a0ff8/test.c#L403) in C +* [from Node library](http://github.com/joyent/node/blob/842eaf4/src/http.js#L284) in Javascript diff --git a/ext/http-parser/http_parser.c b/ext/http-parser/http_parser.c new file mode 100644 index 0000000..895bf0c --- /dev/null +++ b/ext/http-parser/http_parser.c @@ -0,0 +1,2470 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "http_parser.h" +#include +#include +#include +#include +#include +#include + +#ifndef ULLONG_MAX +# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ +#endif + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +#ifndef BIT_AT +# define BIT_AT(a, i) \ + (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ + (1 << ((unsigned int) (i) & 7)))) +#endif + +#ifndef ELEM_AT +# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) +#endif + +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ +} while(0) + +#define CURRENT_STATE() p_state +#define UPDATE_STATE(V) p_state = (enum state) (V); +#define RETURN(V) \ +do { \ + parser->state = CURRENT_STATE(); \ + return (V); \ +} while (0); +#define REEXECUTE() \ + goto reexecute; \ + + +#ifdef __GNUC__ +# define LIKELY(X) __builtin_expect(!!(X), 1) +# define UNLIKELY(X) __builtin_expect(!!(X), 0) +#else +# define LIKELY(X) (X) +# define UNLIKELY(X) (X) +#endif + + +/* Run the notify callback FOR, returning ER if it fails */ +#define CALLBACK_NOTIFY_(FOR, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + UPDATE_STATE(parser->state); \ + \ + /* We either errored above or got paused; get out */ \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ + return (ER); \ + } \ + } \ +} while (0) + +/* Run the notify callback FOR and consume the current byte */ +#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) + +/* Run the notify callback FOR and don't consume the current byte */ +#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) + +/* Run data callback FOR with LEN bytes, returning ER if it fails */ +#define CALLBACK_DATA_(FOR, LEN, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (FOR##_mark) { \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != \ + settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + UPDATE_STATE(parser->state); \ + \ + /* We either errored above or got paused; get out */ \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ + return (ER); \ + } \ + } \ + FOR##_mark = NULL; \ + } \ +} while (0) + +/* Run the data callback FOR and consume the current byte */ +#define CALLBACK_DATA(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) + +/* Run the data callback FOR and don't consume the current byte */ +#define CALLBACK_DATA_NOADVANCE(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) + +/* Set the mark FOR; non-destructive if mark is already set */ +#define MARK(FOR) \ +do { \ + if (!FOR##_mark) { \ + FOR##_mark = p; \ + } \ +} while (0) + +/* Don't allow the total size of the HTTP headers (including the status + * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect + * embedders against denial-of-service attacks where the attacker feeds + * us a never-ending header that the embedder keeps buffering. + * + * This check is arguably the responsibility of embedders but we're doing + * it on the embedder's behalf because most won't bother and this way we + * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger + * than any reasonable request or response so this should never affect + * day-to-day operation. + */ +#define COUNT_HEADER_SIZE(V) \ +do { \ + parser->nread += (V); \ + if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ + SET_ERRNO(HPE_HEADER_OVERFLOW); \ + goto error; \ + } \ +} while (0) + + +#define PROXY_CONNECTION "proxy-connection" +#define CONNECTION "connection" +#define CONTENT_LENGTH "content-length" +#define TRANSFER_ENCODING "transfer-encoding" +#define UPGRADE "upgrade" +#define CHUNKED "chunked" +#define KEEP_ALIVE "keep-alive" +#define CLOSE "close" + + +static const char *method_strings[] = + { +#define XX(num, name, string) #string, + HTTP_METHOD_MAP(XX) +#undef XX + }; + + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +static const char tokens[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, '!', 0, '#', '$', '%', '&', '\'', +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', 0, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'x', 'y', 'z', 0, 0, 0, '^', '_', +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'x', 'y', 'z', 0, '|', 0, '~', 0 }; + + +static const int8_t unhex[256] = + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + +#if HTTP_PARSER_STRICT +# define T(v) 0 +#else +# define T(v) v +#endif + + +static const uint8_t normal_url_char[32] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; + +#undef T + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_first_http_major + , s_res_http_major + , s_res_first_http_minor + , s_res_http_minor + , s_res_first_status_code + , s_res_status_code + , s_res_status_start + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_schema + , s_req_schema_slash + , s_req_schema_slash_slash + , s_req_server_start + , s_req_server + , s_req_server_with_at + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_first_http_major + , s_req_http_major + , s_req_first_http_minor + , s_req_http_minor + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_discard_ws + , s_header_value_discard_ws_almost_done + , s_header_value_discard_lws + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + , s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + + , s_message_done + }; + + +#define PARSING_HEADER(state) (state <= s_headers_done) + + +enum header_states + { h_general = 0 + , h_C + , h_CO + , h_CON + + , h_matching_connection + , h_matching_proxy_connection + , h_matching_content_length + , h_matching_transfer_encoding + , h_matching_upgrade + + , h_connection + , h_content_length + , h_transfer_encoding + , h_upgrade + + , h_matching_transfer_encoding_chunked + , h_matching_connection_token_start + , h_matching_connection_keep_alive + , h_matching_connection_close + , h_matching_connection_upgrade + , h_matching_connection_token + + , h_transfer_encoding_chunked + , h_connection_keep_alive + , h_connection_close + , h_connection_upgrade + }; + +enum http_host_state + { + s_http_host_dead = 1 + , s_http_userinfo_start + , s_http_userinfo + , s_http_host_start + , s_http_host_v6_start + , s_http_host + , s_http_host_v6 + , s_http_host_v6_end + , s_http_host_v6_zone_start + , s_http_host_v6_zone + , s_http_host_port_start + , s_http_host_port +}; + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) +#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ + (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ + (c) == ')') +#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ + (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ + (c) == '$' || (c) == ',') + +#define STRICT_TOKEN(c) (tokens[(unsigned char)c]) + +#if HTTP_PARSER_STRICT +#define TOKEN(c) (tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) +#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') +#else +#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) \ + (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) +#define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + +/** + * Verify that a char is a valid visible (printable) US-ASCII + * character or %x80-FF + **/ +#define IS_HEADER_CHAR(ch) \ + (ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127)) + +#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) + + +#if HTTP_PARSER_STRICT +# define STRICT_CHECK(cond) \ +do { \ + if (cond) { \ + SET_ERRNO(HPE_STRICT); \ + goto error; \ + } \ +} while (0) +# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) +#else +# define STRICT_CHECK(cond) +# define NEW_MESSAGE() start_state +#endif + + +/* Map errno values to strings for human-readable output */ +#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) +}; +#undef HTTP_STRERROR_GEN + +int http_message_needs_eof(const http_parser *parser); + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) +{ + if (ch == ' ' || ch == '\r' || ch == '\n') { + return s_dead; + } + +#if HTTP_PARSER_STRICT + if (ch == '\t' || ch == '\f') { + return s_dead; + } +#endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_server_start; + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return s_dead; + } + + /* FALLTHROUGH */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return s_req_path; + } + + if (ch == '?') { + return s_req_query_string_start; + } + + if (ch == '@') { + return s_req_server_with_at; + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return s_req_server; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + + switch (ch) { + case '?': + return s_req_fragment; + + case '#': + return s; + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + case '#': + return s; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} + +size_t http_parser_execute (http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len) +{ + char c, ch; + int8_t unhex_val; + const char *p = data; + const char *header_field_mark = 0; + const char *header_value_mark = 0; + const char *url_mark = 0; + const char *body_mark = 0; + const char *status_mark = 0; + enum state p_state = (enum state) parser->state; + const unsigned int lenient = parser->lenient_http_headers; + + /* We're in an error state. Don't bother doing anything. */ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return 0; + } + + if (len == 0) { + switch (CURRENT_STATE()) { + case s_body_identity_eof: + /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if + * we got paused. + */ + CALLBACK_NOTIFY_NOADVANCE(message_complete); + return 0; + + case s_dead: + case s_start_req_or_res: + case s_start_res: + case s_start_req: + return 0; + + default: + SET_ERRNO(HPE_INVALID_EOF_STATE); + return 1; + } + } + + + if (CURRENT_STATE() == s_header_field) + header_field_mark = data; + if (CURRENT_STATE() == s_header_value) + header_value_mark = data; + switch (CURRENT_STATE()) { + case s_req_path: + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_server: + case s_req_server_with_at: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + url_mark = data; + break; + case s_res_status: + status_mark = data; + break; + default: + break; + } + + for (p=data; p != data + len; p++) { + ch = *p; + + if (PARSING_HEADER(CURRENT_STATE())) + COUNT_HEADER_SIZE(1); + +reexecute: + switch (CURRENT_STATE()) { + + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + if (LIKELY(ch == CR || ch == LF)) + break; + + SET_ERRNO(HPE_CLOSED_CONNECTION); + goto error; + + case s_start_req_or_res: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (ch == 'H') { + UPDATE_STATE(s_res_or_resp_H); + + CALLBACK_NOTIFY(message_begin); + } else { + parser->type = HTTP_REQUEST; + UPDATE_STATE(s_start_req); + REEXECUTE(); + } + + break; + } + + case s_res_or_resp_H: + if (ch == 'T') { + parser->type = HTTP_RESPONSE; + UPDATE_STATE(s_res_HT); + } else { + if (UNLIKELY(ch != 'E')) { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + parser->type = HTTP_REQUEST; + parser->method = HTTP_HEAD; + parser->index = 2; + UPDATE_STATE(s_req_method); + } + break; + + case s_start_res: + { + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + switch (ch) { + case 'H': + UPDATE_STATE(s_res_H); + break; + + case CR: + case LF: + break; + + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + CALLBACK_NOTIFY(message_begin); + break; + } + + case s_res_H: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_res_HT); + break; + + case s_res_HT: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_res_HTT); + break; + + case s_res_HTT: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_res_HTTP); + break; + + case s_res_HTTP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_res_first_http_major); + break; + + case s_res_first_http_major: + if (UNLIKELY(ch < '0' || ch > '9')) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + UPDATE_STATE(s_res_http_major); + break; + + /* major HTTP version or dot */ + case s_res_http_major: + { + if (ch == '.') { + UPDATE_STATE(s_res_first_http_minor); + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (UNLIKELY(parser->http_major > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_res_first_http_minor: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + UPDATE_STATE(s_res_http_minor); + break; + + /* minor HTTP version or end of request line */ + case s_res_http_minor: + { + if (ch == ' ') { + UPDATE_STATE(s_res_first_status_code); + break; + } + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (UNLIKELY(parser->http_minor > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + case s_res_first_status_code: + { + if (!IS_NUM(ch)) { + if (ch == ' ') { + break; + } + + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + parser->status_code = ch - '0'; + UPDATE_STATE(s_res_status_code); + break; + } + + case s_res_status_code: + { + if (!IS_NUM(ch)) { + switch (ch) { + case ' ': + UPDATE_STATE(s_res_status_start); + break; + case CR: + UPDATE_STATE(s_res_line_almost_done); + break; + case LF: + UPDATE_STATE(s_header_field_start); + break; + default: + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + break; + } + + parser->status_code *= 10; + parser->status_code += ch - '0'; + + if (UNLIKELY(parser->status_code > 999)) { + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + + break; + } + + case s_res_status_start: + { + if (ch == CR) { + UPDATE_STATE(s_res_line_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + break; + } + + MARK(status); + UPDATE_STATE(s_res_status); + parser->index = 0; + break; + } + + case s_res_status: + if (ch == CR) { + UPDATE_STATE(s_res_line_almost_done); + CALLBACK_DATA(status); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + CALLBACK_DATA(status); + break; + } + + break; + + case s_res_line_almost_done: + STRICT_CHECK(ch != LF); + UPDATE_STATE(s_header_field_start); + break; + + case s_start_req: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (UNLIKELY(!IS_ALPHA(ch))) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + parser->method = (enum http_method) 0; + parser->index = 1; + switch (ch) { + case 'A': parser->method = HTTP_ACL; break; + case 'B': parser->method = HTTP_BIND; break; + case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; + case 'D': parser->method = HTTP_DELETE; break; + case 'G': parser->method = HTTP_GET; break; + case 'H': parser->method = HTTP_HEAD; break; + case 'L': parser->method = HTTP_LOCK; /* or LINK */ break; + case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; + case 'N': parser->method = HTTP_NOTIFY; break; + case 'O': parser->method = HTTP_OPTIONS; break; + case 'P': parser->method = HTTP_POST; + /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ + break; + case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break; + case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; + case 'T': parser->method = HTTP_TRACE; break; + case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break; + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + UPDATE_STATE(s_req_method); + + CALLBACK_NOTIFY(message_begin); + + break; + } + + case s_req_method: + { + const char *matcher; + if (UNLIKELY(ch == '\0')) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + matcher = method_strings[parser->method]; + if (ch == ' ' && matcher[parser->index] == '\0') { + UPDATE_STATE(s_req_spaces_before_url); + } else if (ch == matcher[parser->index]) { + ; /* nada */ + } else if (IS_ALPHA(ch)) { + + switch (parser->method << 16 | parser->index << 8 | ch) { +#define XX(meth, pos, ch, new_meth) \ + case (HTTP_##meth << 16 | pos << 8 | ch): \ + parser->method = HTTP_##new_meth; break; + + XX(POST, 1, 'U', PUT) + XX(POST, 1, 'A', PATCH) + XX(CONNECT, 1, 'H', CHECKOUT) + XX(CONNECT, 2, 'P', COPY) + XX(MKCOL, 1, 'O', MOVE) + XX(MKCOL, 1, 'E', MERGE) + XX(MKCOL, 2, 'A', MKACTIVITY) + XX(MKCOL, 3, 'A', MKCALENDAR) + XX(SUBSCRIBE, 1, 'E', SEARCH) + XX(REPORT, 2, 'B', REBIND) + XX(POST, 1, 'R', PROPFIND) + XX(PROPFIND, 4, 'P', PROPPATCH) + XX(PUT, 2, 'R', PURGE) + XX(LOCK, 1, 'I', LINK) + XX(UNLOCK, 2, 'S', UNSUBSCRIBE) + XX(UNLOCK, 2, 'B', UNBIND) + XX(UNLOCK, 3, 'I', UNLINK) +#undef XX + + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (ch == '-' && + parser->index == 1 && + parser->method == HTTP_MKCOL) { + parser->method = HTTP_MSEARCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + ++parser->index; + break; + } + + case s_req_spaces_before_url: + { + if (ch == ' ') break; + + MARK(url); + if (parser->method == HTTP_CONNECT) { + UPDATE_STATE(s_req_server_start); + } + + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + + break; + } + + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + { + switch (ch) { + /* No whitespace allowed here */ + case ' ': + case CR: + case LF: + SET_ERRNO(HPE_INVALID_URL); + goto error; + default: + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + + break; + } + + case s_req_server: + case s_req_server_with_at: + case s_req_path: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + { + switch (ch) { + case ' ': + UPDATE_STATE(s_req_http_start); + CALLBACK_DATA(url); + break; + case CR: + case LF: + parser->http_major = 0; + parser->http_minor = 9; + UPDATE_STATE((ch == CR) ? + s_req_line_almost_done : + s_header_field_start); + CALLBACK_DATA(url); + break; + default: + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + break; + } + + case s_req_http_start: + switch (ch) { + case 'H': + UPDATE_STATE(s_req_http_H); + break; + case ' ': + break; + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + + case s_req_http_H: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_req_http_HT); + break; + + case s_req_http_HT: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_req_http_HTT); + break; + + case s_req_http_HTT: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_req_http_HTTP); + break; + + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_req_first_http_major); + break; + + /* first digit of major HTTP version */ + case s_req_first_http_major: + if (UNLIKELY(ch < '1' || ch > '9')) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + UPDATE_STATE(s_req_http_major); + break; + + /* major HTTP version or dot */ + case s_req_http_major: + { + if (ch == '.') { + UPDATE_STATE(s_req_first_http_minor); + break; + } + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (UNLIKELY(parser->http_major > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_req_first_http_minor: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + UPDATE_STATE(s_req_http_minor); + break; + + /* minor HTTP version or end of request line */ + case s_req_http_minor: + { + if (ch == CR) { + UPDATE_STATE(s_req_line_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + break; + } + + /* XXX allow spaces after digit? */ + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (UNLIKELY(parser->http_minor > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* end of request line */ + case s_req_line_almost_done: + { + if (UNLIKELY(ch != LF)) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + UPDATE_STATE(s_header_field_start); + break; + } + + case s_header_field_start: + { + if (ch == CR) { + UPDATE_STATE(s_headers_almost_done); + break; + } + + if (ch == LF) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + UPDATE_STATE(s_headers_almost_done); + REEXECUTE(); + } + + c = TOKEN(ch); + + if (UNLIKELY(!c)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + MARK(header_field); + + parser->index = 0; + UPDATE_STATE(s_header_field); + + switch (c) { + case 'c': + parser->header_state = h_C; + break; + + case 'p': + parser->header_state = h_matching_proxy_connection; + break; + + case 't': + parser->header_state = h_matching_transfer_encoding; + break; + + case 'u': + parser->header_state = h_matching_upgrade; + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_field: + { + const char* start = p; + for (; p != data + len; p++) { + ch = *p; + c = TOKEN(ch); + + if (!c) + break; + + switch (parser->header_state) { + case h_general: + break; + + case h_C: + parser->index++; + parser->header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + parser->index++; + parser->header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + parser->index++; + switch (c) { + case 'n': + parser->header_state = h_matching_connection; + break; + case 't': + parser->header_state = h_matching_content_length; + break; + default: + parser->header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + parser->index++; + if (parser->index > sizeof(CONNECTION)-1 + || c != CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + parser->index++; + if (parser->index > sizeof(PROXY_CONNECTION)-1 + || c != PROXY_CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + parser->index++; + if (parser->index > sizeof(CONTENT_LENGTH)-1 + || c != CONTENT_LENGTH[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { + parser->header_state = h_content_length; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + parser->index++; + if (parser->index > sizeof(TRANSFER_ENCODING)-1 + || c != TRANSFER_ENCODING[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { + parser->header_state = h_transfer_encoding; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE)-1 + || c != UPGRADE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(UPGRADE)-2) { + parser->header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + } + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) { + --p; + break; + } + + if (ch == ':') { + UPDATE_STATE(s_header_value_discard_ws); + CALLBACK_DATA(header_field); + break; + } + + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + case s_header_value_discard_ws: + if (ch == ' ' || ch == '\t') break; + + if (ch == CR) { + UPDATE_STATE(s_header_value_discard_ws_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_value_discard_lws); + break; + } + + /* FALLTHROUGH */ + + case s_header_value_start: + { + MARK(header_value); + + UPDATE_STATE(s_header_value); + parser->index = 0; + + c = LOWER(ch); + + switch (parser->header_state) { + case h_upgrade: + parser->flags |= F_UPGRADE; + parser->header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + parser->header_state = h_matching_transfer_encoding_chunked; + } else { + parser->header_state = h_general; + } + break; + + case h_content_length: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + if (parser->flags & F_CONTENTLENGTH) { + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + + parser->flags |= F_CONTENTLENGTH; + parser->content_length = ch - '0'; + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + parser->header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + parser->header_state = h_matching_connection_close; + } else if (c == 'u') { + parser->header_state = h_matching_connection_upgrade; + } else { + parser->header_state = h_matching_connection_token; + } + break; + + /* Multi-value `Connection` header */ + case h_matching_connection_token_start: + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_value: + { + const char* start = p; + enum header_states h_state = (enum header_states) parser->header_state; + for (; p != data + len; p++) { + ch = *p; + if (ch == CR) { + UPDATE_STATE(s_header_almost_done); + parser->header_state = h_state; + CALLBACK_DATA(header_value); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_almost_done); + COUNT_HEADER_SIZE(p - start); + parser->header_state = h_state; + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + + if (!lenient && !IS_HEADER_CHAR(ch)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + c = LOWER(ch); + + switch (h_state) { + case h_general: + { + const char* p_cr; + const char* p_lf; + size_t limit = data + len - p; + + limit = MIN(limit, HTTP_MAX_HEADER_SIZE); + + p_cr = (const char*) memchr(p, CR, limit); + p_lf = (const char*) memchr(p, LF, limit); + if (p_cr != NULL) { + if (p_lf != NULL && p_cr >= p_lf) + p = p_lf; + else + p = p_cr; + } else if (UNLIKELY(p_lf != NULL)) { + p = p_lf; + } else { + p = data + len; + } + --p; + + break; + } + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + { + uint64_t t; + + if (ch == ' ') break; + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + parser->content_length = t; + break; + } + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + parser->index++; + if (parser->index > sizeof(CHUNKED)-1 + || c != CHUNKED[parser->index]) { + h_state = h_general; + } else if (parser->index == sizeof(CHUNKED)-2) { + h_state = h_transfer_encoding_chunked; + } + break; + + case h_matching_connection_token_start: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + h_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + h_state = h_matching_connection_close; + } else if (c == 'u') { + h_state = h_matching_connection_upgrade; + } else if (STRICT_TOKEN(c)) { + h_state = h_matching_connection_token; + } else if (c == ' ' || c == '\t') { + /* Skip lws */ + } else { + h_state = h_general; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + parser->index++; + if (parser->index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(KEEP_ALIVE)-2) { + h_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + parser->index++; + if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(CLOSE)-2) { + h_state = h_connection_close; + } + break; + + /* looking for 'Connection: upgrade' */ + case h_matching_connection_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE) - 1 || + c != UPGRADE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(UPGRADE)-2) { + h_state = h_connection_upgrade; + } + break; + + case h_matching_connection_token: + if (ch == ',') { + h_state = h_matching_connection_token_start; + parser->index = 0; + } + break; + + case h_transfer_encoding_chunked: + if (ch != ' ') h_state = h_general; + break; + + case h_connection_keep_alive: + case h_connection_close: + case h_connection_upgrade: + if (ch == ',') { + if (h_state == h_connection_keep_alive) { + parser->flags |= F_CONNECTION_KEEP_ALIVE; + } else if (h_state == h_connection_close) { + parser->flags |= F_CONNECTION_CLOSE; + } else if (h_state == h_connection_upgrade) { + parser->flags |= F_CONNECTION_UPGRADE; + } + h_state = h_matching_connection_token_start; + parser->index = 0; + } else if (ch != ' ') { + h_state = h_matching_connection_token; + } + break; + + default: + UPDATE_STATE(s_header_value); + h_state = h_general; + break; + } + } + parser->header_state = h_state; + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) + --p; + break; + } + + case s_header_almost_done: + { + if (UNLIKELY(ch != LF)) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + UPDATE_STATE(s_header_value_lws); + break; + } + + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') { + UPDATE_STATE(s_header_value_start); + REEXECUTE(); + } + + /* finished the header */ + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + default: + break; + } + + UPDATE_STATE(s_header_field_start); + REEXECUTE(); + } + + case s_header_value_discard_ws_almost_done: + { + STRICT_CHECK(ch != LF); + UPDATE_STATE(s_header_value_discard_lws); + break; + } + + case s_header_value_discard_lws: + { + if (ch == ' ' || ch == '\t') { + UPDATE_STATE(s_header_value_discard_ws); + break; + } else { + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + + /* header value was empty */ + MARK(header_value); + UPDATE_STATE(s_header_field_start); + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + } + + case s_headers_almost_done: + { + STRICT_CHECK(ch != LF); + + if (parser->flags & F_TRAILING) { + /* End of a chunked request */ + UPDATE_STATE(s_message_done); + CALLBACK_NOTIFY_NOADVANCE(chunk_complete); + REEXECUTE(); + } + + /* Cannot use chunked encoding and a content-length header together + per the HTTP specification. */ + if ((parser->flags & F_CHUNKED) && + (parser->flags & F_CONTENTLENGTH)) { + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + + UPDATE_STATE(s_headers_done); + + /* Set this here so that on_headers_complete() callbacks can see it */ + parser->upgrade = + ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == + (F_UPGRADE | F_CONNECTION_UPGRADE) || + parser->method == HTTP_CONNECT); + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + * + * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so + * we have to simulate it by handling a change in errno below. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 2: + parser->upgrade = 1; + + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + SET_ERRNO(HPE_CB_headers_complete); + RETURN(p - data); /* Error */ + } + } + + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + RETURN(p - data); + } + + REEXECUTE(); + } + + case s_headers_done: + { + int hasBody; + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + hasBody = parser->flags & F_CHUNKED || + (parser->content_length > 0 && parser->content_length != ULLONG_MAX); + if (parser->upgrade && (parser->method == HTTP_CONNECT || + (parser->flags & F_SKIPBODY) || !hasBody)) { + /* Exit, the rest of the message is in a different protocol. */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + RETURN((p - data) + 1); + } + + if (parser->flags & F_SKIPBODY) { + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header */ + UPDATE_STATE(s_chunk_size_start); + } else { + if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else if (parser->content_length != ULLONG_MAX) { + /* Content-Length header given and non-zero */ + UPDATE_STATE(s_body_identity); + } else { + if (!http_message_needs_eof(parser)) { + /* Assume content-length 0 - read the next */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else { + /* Read body until EOF */ + UPDATE_STATE(s_body_identity_eof); + } + } + } + + break; + } + + case s_body_identity: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* The difference between advancing content_length and p is because + * the latter will automaticaly advance on the next loop iteration. + * Further, if content_length ends up at 0, we want to see the last + * byte again for our message complete callback. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + UPDATE_STATE(s_message_done); + + /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. + * + * The alternative to doing this is to wait for the next byte to + * trigger the data callback, just as in every other case. The + * problem with this is that this makes it difficult for the test + * harness to distinguish between complete-on-EOF and + * complete-on-length. It's not clear that this distinction is + * important for applications, but let's keep it for now. + */ + CALLBACK_DATA_(body, p - body_mark + 1, p - data); + REEXECUTE(); + } + + break; + } + + /* read until EOF */ + case s_body_identity_eof: + MARK(body); + p = data + len - 1; + + break; + + case s_message_done: + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + if (parser->upgrade) { + /* Exit, the rest of the message is in a different protocol. */ + RETURN((p - data) + 1); + } + break; + + case s_chunk_size_start: + { + assert(parser->nread == 1); + assert(parser->flags & F_CHUNKED); + + unhex_val = unhex[(unsigned char)ch]; + if (UNLIKELY(unhex_val == -1)) { + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; + UPDATE_STATE(s_chunk_size); + break; + } + + case s_chunk_size: + { + uint64_t t; + + assert(parser->flags & F_CHUNKED); + + if (ch == CR) { + UPDATE_STATE(s_chunk_size_almost_done); + break; + } + + unhex_val = unhex[(unsigned char)ch]; + + if (unhex_val == -1) { + if (ch == ';' || ch == ' ') { + UPDATE_STATE(s_chunk_parameters); + break; + } + + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + t = parser->content_length; + t *= 16; + t += unhex_val; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + case s_chunk_parameters: + { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. TODO check for overflow */ + if (ch == CR) { + UPDATE_STATE(s_chunk_size_almost_done); + break; + } + break; + } + + case s_chunk_size_almost_done: + { + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + UPDATE_STATE(s_header_field_start); + } else { + UPDATE_STATE(s_chunk_data); + } + CALLBACK_NOTIFY(chunk_header); + break; + } + + case s_chunk_data: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->flags & F_CHUNKED); + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* See the explanation in s_body_identity for why the content + * length and data pointers are managed this way. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + UPDATE_STATE(s_chunk_data_almost_done); + } + + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + assert(parser->content_length == 0); + STRICT_CHECK(ch != CR); + UPDATE_STATE(s_chunk_data_done); + CALLBACK_DATA(body); + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + parser->nread = 0; + UPDATE_STATE(s_chunk_size_start); + CALLBACK_NOTIFY(chunk_complete); + break; + + default: + assert(0 && "unhandled state"); + SET_ERRNO(HPE_INVALID_INTERNAL_STATE); + goto error; + } + } + + /* Run callbacks for any marks that we have leftover after we ran our of + * bytes. There should be at most one of these set, so it's OK to invoke + * them in series (unset marks will not result in callbacks). + * + * We use the NOADVANCE() variety of callbacks here because 'p' has already + * overflowed 'data' and this allows us to correct for the off-by-one that + * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' + * value that's in-bounds). + */ + + assert(((header_field_mark ? 1 : 0) + + (header_value_mark ? 1 : 0) + + (url_mark ? 1 : 0) + + (body_mark ? 1 : 0) + + (status_mark ? 1 : 0)) <= 1); + + CALLBACK_DATA_NOADVANCE(header_field); + CALLBACK_DATA_NOADVANCE(header_value); + CALLBACK_DATA_NOADVANCE(url); + CALLBACK_DATA_NOADVANCE(body); + CALLBACK_DATA_NOADVANCE(status); + + RETURN(len); + +error: + if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { + SET_ERRNO(HPE_UNKNOWN); + } + + RETURN(p - data); +} + + +/* Does the parser need to see an EOF to find the end of the message? */ +int +http_message_needs_eof (const http_parser *parser) +{ + if (parser->type == HTTP_REQUEST) { + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 || /* Not Modified */ + parser->flags & F_SKIPBODY) { /* response to a HEAD request */ + return 0; + } + + if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { + return 0; + } + + return 1; +} + + +int +http_should_keep_alive (const http_parser *parser) +{ + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } + } else { + /* HTTP/1.0 or earlier */ + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { + return 0; + } + } + + return !http_message_needs_eof(parser); +} + + +const char * +http_method_str (enum http_method m) +{ + return ELEM_AT(method_strings, m, ""); +} + + +void +http_parser_init (http_parser *parser, enum http_parser_type t) +{ + void *data = parser->data; /* preserve application data */ + memset(parser, 0, sizeof(*parser)); + parser->data = data; + parser->type = t; + parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); + parser->http_errno = HPE_OK; +} + +void +http_parser_settings_init(http_parser_settings *settings) +{ + memset(settings, 0, sizeof(*settings)); +} + +const char * +http_errno_name(enum http_errno err) { + assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab)); + return http_strerror_tab[err].name; +} + +const char * +http_errno_description(enum http_errno err) { + assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab)); + return http_strerror_tab[err].description; +} + +static enum http_host_state +http_parse_host_char(enum http_host_state s, const char ch) { + switch(s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return s_http_host_start; + } + + if (IS_USERINFO_CHAR(ch)) { + return s_http_userinfo; + } + break; + + case s_http_host_start: + if (ch == '[') { + return s_http_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + /* FALLTHROUGH */ + case s_http_host_v6_end: + if (ch == ':') { + return s_http_host_port_start; + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_start: + if (IS_HEX(ch) || ch == ':' || ch == '.') { + return s_http_host_v6; + } + + if (s == s_http_host_v6 && ch == '%') { + return s_http_host_v6_zone_start; + } + break; + + case s_http_host_v6_zone: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_zone_start: + /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ + if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' || + ch == '~') { + return s_http_host_v6_zone; + } + break; + + case s_http_host_port: + case s_http_host_port_start: + if (IS_NUM(ch)) { + return s_http_host_port; + } + + break; + + default: + break; + } + return s_http_host_dead; +} + +static int +http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { + enum http_host_state s; + + const char *p; + size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; + + assert(u->field_set & (1 << UF_HOST)); + + u->field_data[UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { + enum http_host_state new_s = http_parse_host_char(s, *p); + + if (new_s == s_http_host_dead) { + return 1; + } + + switch(new_s) { + case s_http_host: + if (s != s_http_host) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + u->field_data[UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + u->field_data[UF_PORT].off = p - buf; + u->field_data[UF_PORT].len = 0; + u->field_set |= (1 << UF_PORT); + } + u->field_data[UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + u->field_data[UF_USERINFO].off = p - buf ; + u->field_data[UF_USERINFO].len = 0; + u->field_set |= (1 << UF_USERINFO); + } + u->field_data[UF_USERINFO].len++; + break; + + default: + break; + } + s = new_s; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return 1; + default: + break; + } + + return 0; +} + +void +http_parser_url_init(struct http_parser_url *u) { + memset(u, 0, sizeof(*u)); +} + +int +http_parser_parse_url(const char *buf, size_t buflen, int is_connect, + struct http_parser_url *u) +{ + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; + int found_at = 0; + + u->port = u->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return 1; + + /* Skip delimeters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = UF_SCHEMA; + break; + + case s_req_server_with_at: + found_at = 1; + + /* FALLTROUGH */ + case s_req_server: + uf = UF_HOST; + break; + + case s_req_path: + uf = UF_PATH; + break; + + case s_req_query_string: + uf = UF_QUERY; + break; + + case s_req_fragment: + uf = UF_FRAGMENT; + break; + + default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = p - buf; + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a schema */ + /* parsing http:///toto will fail */ + if ((u->field_set & (1 << UF_SCHEMA)) && + (u->field_set & (1 << UF_HOST)) == 0) { + return 1; + } + + if (u->field_set & (1 << UF_HOST)) { + if (http_parse_host(buf, u, found_at) != 0) { + return 1; + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { + return 1; + } + + if (u->field_set & (1 << UF_PORT)) { + /* Don't bother with endp; we've already validated the string */ + unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + + u->port = (uint16_t) v; + } + + return 0; +} + +void +http_parser_pause(http_parser *parser, int paused) { + /* Users should only be pausing/unpausing a parser that is not in an error + * state. In non-debug builds, there's not much that we can do about this + * other than ignore it. + */ + if (HTTP_PARSER_ERRNO(parser) == HPE_OK || + HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { + SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); + } else { + assert(0 && "Attempting to pause parser in error state"); + } +} + +int +http_body_is_final(const struct http_parser *parser) { + return parser->state == s_message_done; +} + +unsigned long +http_parser_version(void) { + return HTTP_PARSER_VERSION_MAJOR * 0x10000 | + HTTP_PARSER_VERSION_MINOR * 0x00100 | + HTTP_PARSER_VERSION_PATCH * 0x00001; +} diff --git a/ext/http-parser/http_parser.h b/ext/http-parser/http_parser.h new file mode 100644 index 0000000..45c72a0 --- /dev/null +++ b/ext/http-parser/http_parser.h @@ -0,0 +1,432 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef http_parser_h +#define http_parser_h +#ifdef __cplusplus +extern "C" { +#endif + +/* Also update SONAME in the Makefile whenever you change these. */ +#define HTTP_PARSER_VERSION_MAJOR 2 +#define HTTP_PARSER_VERSION_MINOR 7 +#define HTTP_PARSER_VERSION_PATCH 1 + +#include +#if defined(_WIN32) && !defined(__MINGW32__) && \ + (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) +#include +#include +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef HTTP_PARSER_STRICT +# define HTTP_PARSER_STRICT 1 +#endif + +/* Maximium header size allowed. If the macro is not defined + * before including this header then the default is used. To + * change the maximum header size, define the macro in the build + * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove + * the effective limit on the size of the header, define the macro + * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) + */ +#ifndef HTTP_MAX_HEADER_SIZE +# define HTTP_MAX_HEADER_SIZE (80*1024) +#endif + +typedef struct http_parser http_parser; +typedef struct http_parser_settings http_parser_settings; + + +/* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * Returning `2` from on_headers_complete will tell parser that it should not + * expect neither a body nor any futher responses on this connection. This is + * useful for handling responses to a CONNECT request which may not contain + * `Upgrade` or `Connection: upgrade` headers. + * + * http_data_cb does not return data chunks. It will be called arbitrarily + * many times for each string. E.G. you might get 10 callbacks for "on_url" + * each providing just a few characters more data. + */ +typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); +typedef int (*http_cb) (http_parser*); + + +/* Status Codes */ +#define HTTP_STATUS_MAP(XX) \ + XX(100, CONTINUE, Continue) \ + XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \ + XX(102, PROCESSING, Processing) \ + XX(200, OK, OK) \ + XX(201, CREATED, Created) \ + XX(202, ACCEPTED, Accepted) \ + XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \ + XX(204, NO_CONTENT, No Content) \ + XX(205, RESET_CONTENT, Reset Content) \ + XX(206, PARTIAL_CONTENT, Partial Content) \ + XX(207, MULTI_STATUS, Multi-Status) \ + XX(208, ALREADY_REPORTED, Already Reported) \ + XX(226, IM_USED, IM Used) \ + XX(300, MULTIPLE_CHOICES, Multiple Choices) \ + XX(301, MOVED_PERMANENTLY, Moved Permanently) \ + XX(302, FOUND, Found) \ + XX(303, SEE_OTHER, See Other) \ + XX(304, NOT_MODIFIED, Not Modified) \ + XX(305, USE_PROXY, Use Proxy) \ + XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \ + XX(308, PERMANENT_REDIRECT, Permanent Redirect) \ + XX(400, BAD_REQUEST, Bad Request) \ + XX(401, UNAUTHORIZED, Unauthorized) \ + XX(402, PAYMENT_REQUIRED, Payment Required) \ + XX(403, FORBIDDEN, Forbidden) \ + XX(404, NOT_FOUND, Not Found) \ + XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \ + XX(406, NOT_ACCEPTABLE, Not Acceptable) \ + XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \ + XX(408, REQUEST_TIMEOUT, Request Timeout) \ + XX(409, CONFLICT, Conflict) \ + XX(410, GONE, Gone) \ + XX(411, LENGTH_REQUIRED, Length Required) \ + XX(412, PRECONDITION_FAILED, Precondition Failed) \ + XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \ + XX(414, URI_TOO_LONG, URI Too Long) \ + XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \ + XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \ + XX(417, EXPECTATION_FAILED, Expectation Failed) \ + XX(421, MISDIRECTED_REQUEST, Misdirected Request) \ + XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \ + XX(423, LOCKED, Locked) \ + XX(424, FAILED_DEPENDENCY, Failed Dependency) \ + XX(426, UPGRADE_REQUIRED, Upgrade Required) \ + XX(428, PRECONDITION_REQUIRED, Precondition Required) \ + XX(429, TOO_MANY_REQUESTS, Too Many Requests) \ + XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \ + XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \ + XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \ + XX(501, NOT_IMPLEMENTED, Not Implemented) \ + XX(502, BAD_GATEWAY, Bad Gateway) \ + XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \ + XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \ + XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \ + XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \ + XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ + XX(508, LOOP_DETECTED, Loop Detected) \ + XX(510, NOT_EXTENDED, Not Extended) \ + XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \ + +enum http_status + { +#define XX(num, name, string) HTTP_STATUS_##name = num, + HTTP_STATUS_MAP(XX) +#undef XX + }; + + +/* Request Methods */ +#define HTTP_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + /* pathological */ \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + /* WebDAV */ \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + XX(16, BIND, BIND) \ + XX(17, REBIND, REBIND) \ + XX(18, UNBIND, UNBIND) \ + XX(19, ACL, ACL) \ + /* subversion */ \ + XX(20, REPORT, REPORT) \ + XX(21, MKACTIVITY, MKACTIVITY) \ + XX(22, CHECKOUT, CHECKOUT) \ + XX(23, MERGE, MERGE) \ + /* upnp */ \ + XX(24, MSEARCH, M-SEARCH) \ + XX(25, NOTIFY, NOTIFY) \ + XX(26, SUBSCRIBE, SUBSCRIBE) \ + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ + /* RFC-5789 */ \ + XX(28, PATCH, PATCH) \ + XX(29, PURGE, PURGE) \ + /* CalDAV */ \ + XX(30, MKCALENDAR, MKCALENDAR) \ + /* RFC-2068, section 19.6.1.2 */ \ + XX(31, LINK, LINK) \ + XX(32, UNLINK, UNLINK) \ + +enum http_method + { +#define XX(num, name, string) HTTP_##name = num, + HTTP_METHOD_MAP(XX) +#undef XX + }; + + +enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; + + +/* Flag values for http_parser.flags field */ +enum flags + { F_CHUNKED = 1 << 0 + , F_CONNECTION_KEEP_ALIVE = 1 << 1 + , F_CONNECTION_CLOSE = 1 << 2 + , F_CONNECTION_UPGRADE = 1 << 3 + , F_TRAILING = 1 << 4 + , F_UPGRADE = 1 << 5 + , F_SKIPBODY = 1 << 6 + , F_CONTENTLENGTH = 1 << 7 + }; + + +/* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ +#define HTTP_ERRNO_MAP(XX) \ + /* No error */ \ + XX(OK, "success") \ + \ + /* Callback-related errors */ \ + XX(CB_message_begin, "the on_message_begin callback failed") \ + XX(CB_url, "the on_url callback failed") \ + XX(CB_header_field, "the on_header_field callback failed") \ + XX(CB_header_value, "the on_header_value callback failed") \ + XX(CB_headers_complete, "the on_headers_complete callback failed") \ + XX(CB_body, "the on_body callback failed") \ + XX(CB_message_complete, "the on_message_complete callback failed") \ + XX(CB_status, "the on_status callback failed") \ + XX(CB_chunk_header, "the on_chunk_header callback failed") \ + XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ + \ + /* Parsing-related errors */ \ + XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ + XX(HEADER_OVERFLOW, \ + "too many header bytes seen; overflow detected") \ + XX(CLOSED_CONNECTION, \ + "data received after completed connection: close message") \ + XX(INVALID_VERSION, "invalid HTTP version") \ + XX(INVALID_STATUS, "invalid HTTP status code") \ + XX(INVALID_METHOD, "invalid HTTP method") \ + XX(INVALID_URL, "invalid URL") \ + XX(INVALID_HOST, "invalid host") \ + XX(INVALID_PORT, "invalid port") \ + XX(INVALID_PATH, "invalid path") \ + XX(INVALID_QUERY_STRING, "invalid query string") \ + XX(INVALID_FRAGMENT, "invalid fragment") \ + XX(LF_EXPECTED, "LF character expected") \ + XX(INVALID_HEADER_TOKEN, "invalid character in header") \ + XX(INVALID_CONTENT_LENGTH, \ + "invalid character in content-length header") \ + XX(UNEXPECTED_CONTENT_LENGTH, \ + "unexpected content-length header") \ + XX(INVALID_CHUNK_SIZE, \ + "invalid character in chunk size header") \ + XX(INVALID_CONSTANT, "invalid constant string") \ + XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ + XX(STRICT, "strict mode assertion failed") \ + XX(PAUSED, "parser is paused") \ + XX(UNKNOWN, "an unknown error occurred") + + +/* Define HPE_* values for each errno value above */ +#define HTTP_ERRNO_GEN(n, s) HPE_##n, +enum http_errno { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) +}; +#undef HTTP_ERRNO_GEN + + +/* Get an http_errno value from an http_parser */ +#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) + + +struct http_parser { + /** PRIVATE **/ + unsigned int type : 2; /* enum http_parser_type */ + unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ + unsigned int state : 7; /* enum state from http_parser.c */ + unsigned int header_state : 7; /* enum header_state from http_parser.c */ + unsigned int index : 7; /* index into current matcher */ + unsigned int lenient_http_headers : 1; + + uint32_t nread; /* # bytes read in various scenarios */ + uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ + + /** READ-ONLY **/ + unsigned short http_major; + unsigned short http_minor; + unsigned int status_code : 16; /* responses only */ + unsigned int method : 8; /* requests only */ + unsigned int http_errno : 7; + + /* 1 = Upgrade header was present and the parser has exited because of that. + * 0 = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + unsigned int upgrade : 1; + + /** PUBLIC **/ + void *data; /* A pointer to get hook to the "connection" or "socket" object */ +}; + + +struct http_parser_settings { + http_cb on_message_begin; + http_data_cb on_url; + http_data_cb on_status; + http_data_cb on_header_field; + http_data_cb on_header_value; + http_cb on_headers_complete; + http_data_cb on_body; + http_cb on_message_complete; + /* When on_chunk_header is called, the current chunk length is stored + * in parser->content_length. + */ + http_cb on_chunk_header; + http_cb on_chunk_complete; +}; + + +enum http_parser_url_fields + { UF_SCHEMA = 0 + , UF_HOST = 1 + , UF_PORT = 2 + , UF_PATH = 3 + , UF_QUERY = 4 + , UF_FRAGMENT = 5 + , UF_USERINFO = 6 + , UF_MAX = 7 + }; + + +/* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +struct http_parser_url { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; +}; + + +/* Returns the library version. Bits 16-23 contain the major version number, + * bits 8-15 the minor version number and bits 0-7 the patch level. + * Usage example: + * + * unsigned long version = http_parser_version(); + * unsigned major = (version >> 16) & 255; + * unsigned minor = (version >> 8) & 255; + * unsigned patch = version & 255; + * printf("http_parser v%u.%u.%u\n", major, minor, patch); + */ +unsigned long http_parser_version(void); + +void http_parser_init(http_parser *parser, enum http_parser_type type); + + +/* Initialize http_parser_settings members to 0 + */ +void http_parser_settings_init(http_parser_settings *settings); + + +/* Executes the parser. Returns number of parsed bytes. Sets + * `parser->http_errno` on error. */ +size_t http_parser_execute(http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len); + + +/* If http_should_keep_alive() in the on_headers_complete or + * on_message_complete callback returns 0, then this should be + * the last message on the connection. + * If you are the server, respond with the "Connection: close" header. + * If you are the client, close the connection. + */ +int http_should_keep_alive(const http_parser *parser); + +/* Returns a string version of the HTTP method. */ +const char *http_method_str(enum http_method m); + +/* Return a string name of the given error */ +const char *http_errno_name(enum http_errno err); + +/* Return a string description of the given error */ +const char *http_errno_description(enum http_errno err); + +/* Initialize all http_parser_url members to 0 */ +void http_parser_url_init(struct http_parser_url *u); + +/* Parse a URL; return nonzero on failure */ +int http_parser_parse_url(const char *buf, size_t buflen, + int is_connect, + struct http_parser_url *u); + +/* Pause or un-pause the parser; a nonzero value pauses */ +void http_parser_pause(http_parser *parser, int paused); + +/* Checks if this is the final chunk of the body. */ +int http_body_is_final(const http_parser *parser); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/ext/installfiles/linux/zerotier-containerized/Dockerfile b/ext/installfiles/linux/zerotier-containerized/Dockerfile new file mode 100644 index 0000000..678216d --- /dev/null +++ b/ext/installfiles/linux/zerotier-containerized/Dockerfile @@ -0,0 +1,20 @@ +FROM alpine:latest +MAINTAINER Adam Ierymenko + +LABEL version="1.1.14" +LABEL description="Containerized ZeroTier One for use on CoreOS or other Docker-only Linux hosts." + +# Uncomment to build in container +#RUN apk add --update alpine-sdk linux-headers + +RUN apk add --update libgcc libstdc++ + +ADD zerotier-one / +RUN chmod 0755 /zerotier-one +RUN ln -sf /zerotier-one /zerotier-cli +RUN mkdir -p /var/lib/zerotier-one + +ADD main.sh / +RUN chmod 0755 /main.sh + +ENTRYPOINT /main.sh diff --git a/ext/installfiles/linux/zerotier-containerized/main.sh b/ext/installfiles/linux/zerotier-containerized/main.sh new file mode 100755 index 0000000..685a689 --- /dev/null +++ b/ext/installfiles/linux/zerotier-containerized/main.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +export PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin + +if [ ! -e /dev/net/tun ]; then + echo 'FATAL: cannot start ZeroTier One in container: /dev/net/tun not present.' + exit 1 +fi + +exec /zerotier-one diff --git a/ext/installfiles/linux/zerotier-one.init.rhel6 b/ext/installfiles/linux/zerotier-one.init.rhel6 new file mode 100755 index 0000000..3ff2f18 --- /dev/null +++ b/ext/installfiles/linux/zerotier-one.init.rhel6 @@ -0,0 +1,138 @@ +#!/bin/bash +# +# zerotier-one Start the ZeroTier One network virtualization service +# +# chkconfig: 2345 55 25 +# description: ZeroTier One allows systems to join and participate in \ +# ZeroTier virtual networks. See https://www.zerotier.com/ +# +# processname: zerotier-one +# config: /var/lib/zerotier-one/identity.public +# config: /var/lib/zerotier-one/identity.secret +# config: /var/lib/zerotier-one/local.conf +# config: /var/lib/zerotier-one/authtoken.secret +# pidfile: /var/lib/zerotier-one/zerotier-one.pid + +### BEGIN INIT INFO +# Provides: zerotier-one +# Required-Start: $local_fs $network $syslog +# Required-Stop: $local_fs $syslog +# Should-Start: $syslog +# Should-Stop: $network $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start the ZeroTier One network virtualization service +# Description: ZeroTier One allows systems to join and participate in +# ZeroTier virtual networks. See https://www.zerotier.com/ +### END INIT INFO + +# source function library +. /etc/rc.d/init.d/functions + +# pull in sysconfig settings +[ -f /etc/sysconfig/zerotier-one ] && . /etc/sysconfig/zerotier-one + +RETVAL=0 +prog="zerotier-one" +lockfile=/var/lock/subsys/$prog +ZT="/usr/sbin/zerotier-one" +PID_FILE=/var/lib/zerotier-one/zerotier-one.pid + +runlevel=$(set -- $(runlevel); eval "echo \$$#" ) + +start() +{ + [ -x $ZT ] || exit 5 + echo -n $"Starting $prog: " + $ZT $ZT_OPTIONS -d && success || failure + RETVAL=$? + [ $RETVAL -eq 0 ] && touch $lockfile + echo + return $RETVAL +} + +stop() +{ + echo -n $"Stopping $prog: " + killproc -p $PID_FILE $ZT + RETVAL=$? + if [ "x$runlevel" = x0 -o "x$runlevel" = x6 ] ; then + trap '' TERM + killall $prog 2>/dev/null + trap TERM + fi + [ $RETVAL -eq 0 ] && rm -f $lockfile + echo +} + +reload() +{ + stop + start +} + +restart() { + stop + start +} + +force_reload() { + restart +} + +rh_status() { + status -p $PID_FILE zerotier-one +} + +rh_status_q() { + rh_status >/dev/null 2>&1 +} + +case "$1" in + start) + rh_status_q && exit 0 + start + ;; + stop) + if ! rh_status_q; then + rm -f $lockfile + exit 0 + fi + stop + ;; + restart) + restart + ;; + reload) + rh_status_q || exit 7 + reload + ;; + force-reload) + force_reload + ;; + condrestart|try-restart) + rh_status_q || exit 0 + if [ -f $lockfile ] ; then + do_restart_sanity_check + if [ $RETVAL -eq 0 ] ; then + stop + # avoid race + sleep 3 + start + else + RETVAL=6 + fi + fi + ;; + status) + rh_status + RETVAL=$? + if [ $RETVAL -eq 3 -a -f $lockfile ] ; then + RETVAL=2 + fi + ;; + *) + echo $"Usage: $0 {start|stop|restart|reload|force-reload|condrestart|try-restart|status}" + RETVAL=2 +esac +exit $RETVAL diff --git a/ext/installfiles/mac-update/updater.tmpl.sh b/ext/installfiles/mac-update/updater.tmpl.sh new file mode 100644 index 0000000..0b07f6d --- /dev/null +++ b/ext/installfiles/mac-update/updater.tmpl.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +export PATH=/bin:/usr/bin:/sbin:/usr/sbin +shopt -s expand_aliases + +if [ "$UID" -ne 0 ]; then + echo '*** Auto-updater must be run as root.' + exit 1 +fi + +scriptPath="`dirname "$0"`/`basename "$0"`" +if [ ! -s "$scriptPath" ]; then + scriptPath="$0" + if [ ! -s "$scriptPath" ]; then + echo "*** Auto-updater cannot determine its own path; $scriptPath is not readable." + exit 2 + fi +fi + +endMarkerIndex=`grep -a -b -E '^################' "$scriptPath" | head -c 16 | cut -d : -f 1` +if [ "$endMarkerIndex" -le 100 ]; then + echo 'Internal error: unable to find end of script / start of binary data marker.' + exit 2 +fi +blobStart=`expr $endMarkerIndex + 17` +if [ "$blobStart" -le "$endMarkerIndex" ]; then + echo 'Internal error: unable to find end of script / start of binary data marker.' + exit 2 +fi + +rm -f /tmp/ZeroTierOne-update.pkg +tail -c +$blobStart "$scriptPath" >/tmp/ZeroTierOne-update.pkg +chmod 0600 /tmp/ZeroTierOne-update.pkg + +if [ -s /tmp/ZeroTierOne-update.pkg ]; then + rm -f '/Library/Application Support/ZeroTier/One/latest-update.exe' '/Library/Application Support/ZeroTier/One/latest-update.json' /tmp/ZeroTierOne-update.log + installer -verbose -pkg /tmp/ZeroTierOne-update.pkg -target / >/tmp/ZeroTierOne-update.log 2>&1 + rm -f /tmp/ZeroTierOne-update.pkg + exit 0 +else + echo '*** Error self-unpacking update!' + exit 3 +fi + +# Do not remove the last line or add a carriage return to it! The installer +# looks for an unterminated line beginning with 16 #'s in itself to find +# the binary blob data, which is appended after it. + +################ \ No newline at end of file diff --git a/ext/installfiles/mac/ZeroTier One.pkgproj b/ext/installfiles/mac/ZeroTier One.pkgproj new file mode 100755 index 0000000..96b1338 --- /dev/null +++ b/ext/installfiles/mac/ZeroTier One.pkgproj @@ -0,0 +1,872 @@ + + + + + PROJECT + + PACKAGE_FILES + + DEFAULT_INSTALL_LOCATION + / + HIERARCHY + + CHILDREN + + + CHILDREN + + + CHILDREN + + GID + 80 + PATH + Utilities + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 80 + PATH + ../../../macui/build/Release/ZeroTier One.app + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + GID + 80 + PATH + Applications + PATH_TYPE + 0 + PERMISSIONS + 509 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + + CHILDREN + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + get-proxy-settings.sh + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + launch.sh + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + ../../bin/tap-mac/tap.kext + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + uninstall.sh + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + ../../../zerotier-one + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + GID + 80 + PATH + One + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 2 + UID + 0 + + + GID + 80 + PATH + ZeroTier + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 2 + UID + 0 + + + GID + 80 + PATH + Application Support + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Automator + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Documentation + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Filesystems + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Frameworks + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Input Methods + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Internet Plug-Ins + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + LaunchAgents + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + com.zerotier.one.plist + PATH_TYPE + 1 + PERMISSIONS + 420 + TYPE + 3 + UID + 0 + + + GID + 0 + PATH + LaunchDaemons + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PreferencePanes + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Preferences + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 80 + PATH + Printers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PrivilegedHelperTools + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickLook + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickTime + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Screen Savers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Scripts + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Services + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Widgets + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + Library + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + Extensions + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + Library + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + System + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + Shared + PATH_TYPE + 0 + PERMISSIONS + 1023 + TYPE + 1 + UID + 0 + + + GID + 80 + PATH + Users + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + / + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + PAYLOAD_TYPE + 0 + VERSION + 3 + + PACKAGE_SCRIPTS + + POSTINSTALL_PATH + + PATH + postinst.sh + PATH_TYPE + 1 + + PREINSTALL_PATH + + PATH + preinst.sh + PATH_TYPE + 1 + + RESOURCES + + + PACKAGE_SETTINGS + + AUTHENTICATION + 1 + CONCLUSION_ACTION + 0 + IDENTIFIER + com.zerotier.pkg.ZeroTierOne + OVERWRITE_PERMISSIONS + + VERSION + 1.2.4 + + PROJECT_COMMENTS + + NOTES + + PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M + IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv + c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l + cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7 + IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250 + ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp + dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u + dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD + b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE1MDQuNzYiPgo8c3R5bGUg + dHlwZT0idGV4dC9jc3MiPgpwLnAxIHttYXJnaW46IDAuMHB4IDAu + MHB4IDAuMHB4IDAuMHB4OyBsaW5lLWhlaWdodDogMTQuMHB4OyBm + b250OiAxMi4wcHggSGVsdmV0aWNhOyBjb2xvcjogIzAwMDAwMDsg + LXdlYmtpdC10ZXh0LXN0cm9rZTogIzAwMDAwMH0Kc3Bhbi5zMSB7 + Zm9udC1rZXJuaW5nOiBub25lfQo8L3N0eWxlPgo8L2hlYWQ+Cjxi + b2R5Pgo8cCBjbGFzcz0icDEiPjxzcGFuIGNsYXNzPSJzMSI+WmVy + b1RpZXIgT25lIC0gTmV0d29yayBWaXJ0dWFsaXphdGlvbiBFdmVy + eXdoZXJlPC9zcGFuPjwvcD4KPHAgY2xhc3M9InAxIj48c3BhbiBj + bGFzcz0iczEiPihjKTIwMTEtMjAxNyBaZXJvVGllciwgSW5jLjwv + c3Bhbj48L3A+CjxwIGNsYXNzPSJwMSI+PHNwYW4gY2xhc3M9InMx + Ij5jb250YWN0QHplcm90aWVyLmNvbTwvc3Bhbj48L3A+CjxwIGNs + YXNzPSJwMSI+PHNwYW4gY2xhc3M9InMxIj48YnI+Cjwvc3Bhbj48 + L3A+CjxwIGNsYXNzPSJwMSI+PHNwYW4gY2xhc3M9InMxIj5UbyB1 + bmluc3RhbGwgbWFudWFsbHksIHR5cGUgdGhlIGZvbGxvd2luZyBp + biBhIHRlcm1pbmFsIHdpbmRvdzo8L3NwYW4+PC9wPgo8cCBjbGFz + cz0icDEiPjxzcGFuIGNsYXNzPSJzMSI+PGJyPgo8L3NwYW4+PC9w + Pgo8cCBjbGFzcz0icDEiPjxzcGFuIGNsYXNzPSJzMSI+c3VkbyAi + L0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9aZXJvVGllci9P + bmUvdW5pbnN0YWxsLnNoIjwvc3Bhbj48L3A+CjwvYm9keT4KPC9o + dG1sPgo= + + + PROJECT_SETTINGS + + BUILD_PATH + + PATH + ../../.. + PATH_TYPE + 1 + + EXCLUDED_FILES + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + .DS_Store + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove .DS_Store files + PROXY_TOOLTIP + Remove ".DS_Store" files created by the Finder. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + .pbdevelopment + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove .pbdevelopment files + PROXY_TOOLTIP + Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + CVS + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .cvsignore + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + .cvspass + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + .svn + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .git + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .gitignore + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove SCM metadata + PROXY_TOOLTIP + Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + classes.nib + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + designable.db + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + info.nib + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Optimize nib files + PROXY_TOOLTIP + Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + Resources Disabled + TYPE + 1 + + + PROTECTED + + PROXY_NAME + Remove Resources Disabled folders + PROXY_TOOLTIP + Remove "Resources Disabled" folders. + STATE + + + + SEPARATOR + + + + NAME + ZeroTier One + + + TYPE + 1 + VERSION + 2 + + diff --git a/ext/installfiles/mac/com.zerotier.one.plist b/ext/installfiles/mac/com.zerotier.one.plist new file mode 100644 index 0000000..e3c227d --- /dev/null +++ b/ext/installfiles/mac/com.zerotier.one.plist @@ -0,0 +1,22 @@ + + + + + Label + com.zerotier.one + UserName + root + ProgramArguments + + /Library/Application Support/ZeroTier/One/launch.sh + + WorkingDirectory + /Library/Application Support/ZeroTier/One + StandardOutPath + /dev/null + StandardErrorPath + /dev/null + KeepAlive + + + diff --git a/ext/installfiles/mac/get-proxy-settings.sh b/ext/installfiles/mac/get-proxy-settings.sh new file mode 100755 index 0000000..16ba0b4 --- /dev/null +++ b/ext/installfiles/mac/get-proxy-settings.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Outputs host and port for system HTTP proxy or zeroes if none or not +# configured. + +export PATH=/bin:/usr/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/sbin + +enabled=`system_profiler SPNetworkDataType|grep "HTTP Proxy Enabled"|awk {'sub(/^.*:[ \t]*/, "", $0); print $0;'} 2>/dev/null` +port=`system_profiler SPNetworkDataType|grep "HTTP Proxy Port"|awk {'sub(/^.*:[ \t]*/, "", $0); print $0;'} 2>/dev/null` +serv=`system_profiler SPNetworkDataType|grep "HTTP Proxy Server"|awk {'sub(/^.*:[ \t]*/, "", $0); print $0;'} 2>/dev/null` + +if [ "$enabled" = "Yes" ]; then + if [ "$serv" ]; then + if [ ! "$port" ]; then + port=80 + fi + + echo $serv $port + else + echo 0.0.0.0 0 + fi +else + echo 0.0.0.0 0 +fi + +exit 0 diff --git a/ext/installfiles/mac/launch.sh b/ext/installfiles/mac/launch.sh new file mode 100755 index 0000000..41c4b9c --- /dev/null +++ b/ext/installfiles/mac/launch.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +zthome="/Library/Application Support/ZeroTier/One" +export PATH="$zthome:/bin:/usr/bin:/sbin:/usr/sbin" + +# Launch ZeroTier One (not as daemon... launchd monitors it) +exec zerotier-one diff --git a/ext/installfiles/mac/postinst.sh b/ext/installfiles/mac/postinst.sh new file mode 100755 index 0000000..2e4f591 --- /dev/null +++ b/ext/installfiles/mac/postinst.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +export PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin + +OSX_RELEASE=`sw_vers -productVersion | cut -d . -f 1,2` + +launchctl unload /Library/LaunchDaemons/com.zerotier.one.plist >>/dev/null 2>&1 +sleep 1 + +cd "/Library/Application Support/ZeroTier/One" + +if [ "$OSX_RELEASE" = "10.7" ]; then + # OSX 10.7 cannot use the new tap driver since the new way of kext signing + # is not backward compatible. Pull the old one for 10.7 users and replace. + # We use https to fetch and check hash as an extra added measure. + rm -f tap.kext.10_7.tar.gz + curl -s https://download.zerotier.com/tap.kext.10_7.tar.gz >tap.kext.10_7.tar.gz + if [ -s tap.kext.10_7.tar.gz -a "`shasum -a 256 tap.kext.10_7.tar.gz | cut -d ' ' -f 1`" = "e133d4832cef571621d3618f417381b44f51a76ed625089fb4e545e65d3ef2a9" ]; then + rm -rf tap.kext + tar -xzf tap.kext.10_7.tar.gz + fi + rm -f tap.kext.10_7.tar.gz +fi + +rm -rf node.log node.log.old root-topology shutdownIfUnreadable autoupdate.log updates.d ui peers.save +chown -R 0 tap.kext +chgrp -R 0 tap.kext +if [ ! -f authtoken.secret ]; then + head -c 4096 /dev/urandom | md5 | head -c 24 >authtoken.secret + chown 0 authtoken.secret + chgrp 0 authtoken.secret + chmod 0600 authtoken.secret +fi +rm -f zerotier-cli zerotier-idtool +ln -sf zerotier-one zerotier-cli +ln -sf zerotier-one zerotier-idtool + +mkdir -p /usr/local/bin +cd /usr/local/bin +rm -f zerotier-cli zerotier-idtool +ln -sf "/Library/Application Support/ZeroTier/One/zerotier-one" zerotier-cli +ln -sf "/Library/Application Support/ZeroTier/One/zerotier-one" zerotier-idtool + +launchctl load /Library/LaunchDaemons/com.zerotier.one.plist >>/dev/null 2>&1 + +exit 0 diff --git a/ext/installfiles/mac/preinst.sh b/ext/installfiles/mac/preinst.sh new file mode 100755 index 0000000..c2cb494 --- /dev/null +++ b/ext/installfiles/mac/preinst.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +export PATH=/bin:/usr/bin:/sbin:/usr/sbin + +if [ -f /Library/LaunchDaemons/com.zerotier.one.plist ]; then + launchctl unload /Library/LaunchDaemons/com.zerotier.one.plist >>/dev/null 2>&1 +fi + +sleep 1 + +if [ -d "/Library/Application Support/ZeroTier/One" ]; then + cd "/Library/Application Support/ZeroTier/One" + if [ -f "zerotier-one.pid" ]; then + ztpid=`cat zerotier-one.pid` + if [ "$ztpid" -gt "0" ]; then + kill `cat zerotier-one.pid` + fi + fi +fi + +sleep 1 + +cd "/Applications" +rm -rf "ZeroTier One.app" + +exit 0 diff --git a/ext/installfiles/mac/uninstall.sh b/ext/installfiles/mac/uninstall.sh new file mode 100755 index 0000000..9bf5d6f --- /dev/null +++ b/ext/installfiles/mac/uninstall.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +export PATH=/bin:/usr/bin:/sbin:/usr/sbin + +if [ "$UID" -ne 0 ]; then + echo "Must be run as root; try: sudo $0" + exit 1 +fi + +if [ ! -f '/Library/LaunchDaemons/com.zerotier.one.plist' ]; then + echo 'ZeroTier One does not seem to be installed.' + exit 1 +fi + +cd / + +echo 'Stopping any running ZeroTier One service...' +launchctl unload '/Library/LaunchDaemons/com.zerotier.one.plist' >>/dev/null 2>&1 +sleep 1 +killall -TERM zerotier-one >>/dev/null 2>&1 +sleep 1 +killall -KILL zerotier-one >>/dev/null 2>&1 + +echo "Making sure kext is unloaded..." +kextunload '/Library/Application Support/ZeroTier/One/tap.kext' >>/dev/null 2>&1 + +echo "Removing ZeroTier One files..." + +rm -rf '/Applications/ZeroTier One.app' +rm -f '/usr/bin/zerotier-one' '/usr/bin/zerotier-idtool' '/usr/bin/zerotier-cli' '/Library/LaunchDaemons/com.zerotier.one.plist' + +cd '/Library/Application Support/ZeroTier/One' +if [ "`pwd`" = '/Library/Application Support/ZeroTier/One' ]; then + rm -rf *.d *.sh *.log *.old *.kext *.conf *.pkg *.dmg *.pid *.port *.save *.bin planet zerotier-* devicemap +fi + +echo 'Uninstall complete.' +echo +echo 'Your identity and secret authentication token have been preserved in:' +echo ' /Library/Application Support/ZeroTier/One' +echo +echo 'You can delete this folder and its contents if you do not intend to re-use' +echo 'them.' +echo + +exit 0 diff --git a/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x64).aip b/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x64).aip new file mode 100644 index 0000000..db8566c --- /dev/null +++ b/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x64).aip @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x86).aip b/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x86).aip new file mode 100644 index 0000000..b83b382 --- /dev/null +++ b/ext/installfiles/windows/ZeroTier One Virtual Network Port (NDIS6_x86).aip @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext/installfiles/windows/ZeroTier One.aip b/ext/installfiles/windows/ZeroTier One.aip new file mode 100644 index 0000000..a63fa2b --- /dev/null +++ b/ext/installfiles/windows/ZeroTier One.aip @@ -0,0 +1,462 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext/installfiles/windows/chocolatey/zerotier-one/tools/LICENSE.txt b/ext/installfiles/windows/chocolatey/zerotier-one/tools/LICENSE.txt new file mode 100644 index 0000000..ce0564a --- /dev/null +++ b/ext/installfiles/windows/chocolatey/zerotier-one/tools/LICENSE.txt @@ -0,0 +1,11 @@ +From: https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/COPYING + +LICENSE + +ZeroTier One 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. + +See the file ‘LICENSE.GPL-3’ for the text of the GNU GPL version 3. +If that file is not present, see . diff --git a/ext/installfiles/windows/chocolatey/zerotier-one/tools/VERIFICATION.txt b/ext/installfiles/windows/chocolatey/zerotier-one/tools/VERIFICATION.txt new file mode 100644 index 0000000..0a5bc76 --- /dev/null +++ b/ext/installfiles/windows/chocolatey/zerotier-one/tools/VERIFICATION.txt @@ -0,0 +1,5 @@ +VERIFICATION +Verification is intended to assist the Chocolatey moderators and community +in verifying that this package's contents are trustworthy. + +Our MSI installer should be signed by ZeroTier, Inc. using a certificate from DigiCert. diff --git a/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyinstall.ps1 b/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyinstall.ps1 new file mode 100644 index 0000000..f8a7457 --- /dev/null +++ b/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyinstall.ps1 @@ -0,0 +1,8 @@ +$packageName = 'zerotier-one' +$installerType = 'msi' +$url = 'https://download.zerotier.com/RELEASES/1.1.14/dist/ZeroTier%20One.msi' +$url64 = 'https://download.zerotier.com/RELEASES/1.1.14/dist/ZeroTier%20One.msi' +$silentArgs = '/quiet' +$validExitCodes = @(0,3010) + +Install-ChocolateyPackage $packageName $installerType $silentArgs $url $url64 -validExitCodes $validExitCodes diff --git a/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyuninstall.ps1 b/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyuninstall.ps1 new file mode 100644 index 0000000..81f7a5a --- /dev/null +++ b/ext/installfiles/windows/chocolatey/zerotier-one/tools/chocolateyuninstall.ps1 @@ -0,0 +1,30 @@ +$ErrorActionPreference = 'Stop'; + +$packageName = 'zerotier-one' +$softwareName = 'ZeroTier One*' +$installerType = 'MSI' + +$silentArgs = '/qn /norestart' +$validExitCodes = @(0, 3010, 1605, 1614, 1641) +$uninstalled = $false + +[array]$key = Get-UninstallRegistryKey -SoftwareName $softwareName + +if ($key.Count -eq 1) { + $key | % { + $silentArgs = "$($_.PSChildName) $silentArgs" + $file = '' + Uninstall-ChocolateyPackage -PackageName $packageName ` + -FileType $installerType ` + -SilentArgs "$silentArgs" ` + -ValidExitCodes $validExitCodes ` + -File "$file" + } +} elseif ($key.Count -eq 0) { + Write-Warning "$packageName has already been uninstalled by other means." +} elseif ($key.Count -gt 1) { + Write-Warning "$key.Count matches found!" + Write-Warning "To prevent accidental data loss, no programs will be uninstalled." + Write-Warning "Please alert package maintainer the following keys were matched:" + $key | % {Write-Warning "- $_.DisplayName"} +} diff --git a/ext/installfiles/windows/chocolatey/zerotier-one/zerotier-one.nuspec b/ext/installfiles/windows/chocolatey/zerotier-one/zerotier-one.nuspec new file mode 100644 index 0000000..32fa5a9 --- /dev/null +++ b/ext/installfiles/windows/chocolatey/zerotier-one/zerotier-one.nuspec @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + zerotier-one + + + + 1.2.4 + + + + + + + + zerotier-one (Install) + ZeroTier, Inc. + + https://www.zerotier.com/ + + + + + + + + + zerotier-one admin + ZeroTier One Virtual Network Endpoint for Windows + ZeroTier is a smart switch for Earth with VLAN capability. See https://www.zerotier.com/ for more information. + + + + + + + + + + + + + + + + + diff --git a/ext/json/LICENSE.MIT b/ext/json/LICENSE.MIT new file mode 100644 index 0000000..e2ac489 --- /dev/null +++ b/ext/json/LICENSE.MIT @@ -0,0 +1,22 @@ +The library is licensed under the MIT License +: + +Copyright (c) 2013-2016 Niels Lohmann + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ext/json/README.md b/ext/json/README.md new file mode 100644 index 0000000..4bcbe97 --- /dev/null +++ b/ext/json/README.md @@ -0,0 +1,538 @@ +[![JSON for Modern C++](https://raw.githubusercontent.com/nlohmann/json/master/doc/json.gif)](https://github.com/nlohmann/json/releases) + +[![Build Status](https://travis-ci.org/nlohmann/json.svg?branch=master)](https://travis-ci.org/nlohmann/json) +[![Build Status](https://ci.appveyor.com/api/projects/status/1acb366xfyg3qybk/branch/develop?svg=true)](https://ci.appveyor.com/project/nlohmann/json) +[![Coverage Status](https://img.shields.io/coveralls/nlohmann/json.svg)](https://coveralls.io/r/nlohmann/json) +[![Try online](https://img.shields.io/badge/try-online-blue.svg)](http://melpon.org/wandbox/permlink/fsf5FqYe6GoX68W6) +[![Documentation](https://img.shields.io/badge/docs-doxygen-blue.svg)](http://nlohmann.github.io/json) +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nlohmann/json/master/LICENSE.MIT) +[![Github Releases](https://img.shields.io/github/release/nlohmann/json.svg)](https://github.com/nlohmann/json/releases) +[![Github Issues](https://img.shields.io/github/issues/nlohmann/json.svg)](http://github.com/nlohmann/json/issues) +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/289/badge)](https://bestpractices.coreinfrastructure.org/projects/289) + +## Design goals + +There are myriads of [JSON](http://json.org) libraries out there, and each may even have its reason to exist. Our class had these design goals: + +- **Intuitive syntax**. In languages such as Python, JSON feels like a first class data type. We used all the operator magic of modern C++ to achieve the same feeling in your code. Check out the [examples below](#examples) and you'll know what I mean. + +- **Trivial integration**. Our whole code consists of a single header file [`json.hpp`](https://github.com/nlohmann/json/blob/develop/src/json.hpp). That's it. No library, no subproject, no dependencies, no complex build system. The class is written in vanilla C++11. All in all, everything should require no adjustment of your compiler flags or project settings. + +- **Serious testing**. Our class is heavily [unit-tested](https://github.com/nlohmann/json/blob/master/test/src/unit.cpp) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](http://valgrind.org) that there are no memory leaks. To maintain high quality, the project is following the [Core Infrastructure Initiative (CII) best practices](https://bestpractices.coreinfrastructure.org/projects/289). + +Other aspects were not so important to us: + +- **Memory efficiency**. Each JSON object has an overhead of one pointer (the maximal size of a union) and one enumeration element (1 byte). The default generalization uses the following C++ data types: `std::string` for strings, `int64_t`, `uint64_t` or `double` for numbers, `std::map` for objects, `std::vector` for arrays, and `bool` for Booleans. However, you can template the generalized class `basic_json` to your needs. + +- **Speed**. We currently implement the parser as naive [recursive descent parser](http://en.wikipedia.org/wiki/Recursive_descent_parser) with hand coded string handling. It is fast enough, but a [LALR-parser](http://en.wikipedia.org/wiki/LALR_parser) may be even faster (but would consist of more files which makes the integration harder). + +See the [contribution guidelines](https://github.com/nlohmann/json/blob/master/.github/CONTRIBUTING.md#please-dont) for more information. + + +## Integration + +The single required source, file `json.hpp` is in the `src` directory or [released here](https://github.com/nlohmann/json/releases). All you need to do is add + +```cpp +#include "json.hpp" + +// for convenience +using json = nlohmann::json; +``` + +to the files you want to use JSON objects. That's it. Do not forget to set the necessary switches to enable C++11 (e.g., `-std=c++11` for GCC and Clang). + +:beer: If you are using OS X and [Homebrew](http://brew.sh), just type `brew tap nlohmann/json` and `brew install nlohmann_json` and you're set. If you want the bleeding edge rather than the latest release, use `brew install nlohmann_json --HEAD`. + + +## Examples + +Here are some examples to give you an idea how to use the class. + +Assume you want to create the JSON object + +```json +{ + "pi": 3.141, + "happy": true, + "name": "Niels", + "nothing": null, + "answer": { + "everything": 42 + }, + "list": [1, 0, 2], + "object": { + "currency": "USD", + "value": 42.99 + } +} +``` + +With the JSON class, you could write: + +```cpp +// create an empty structure (null) +json j; + +// add a number that is stored as double (note the implicit conversion of j to an object) +j["pi"] = 3.141; + +// add a Boolean that is stored as bool +j["happy"] = true; + +// add a string that is stored as std::string +j["name"] = "Niels"; + +// add another null object by passing nullptr +j["nothing"] = nullptr; + +// add an object inside the object +j["answer"]["everything"] = 42; + +// add an array that is stored as std::vector (using an initializer list) +j["list"] = { 1, 0, 2 }; + +// add another object (using an initializer list of pairs) +j["object"] = { {"currency", "USD"}, {"value", 42.99} }; + +// instead, you could also write (which looks very similar to the JSON above) +json j2 = { + {"pi", 3.141}, + {"happy", true}, + {"name", "Niels"}, + {"nothing", nullptr}, + {"answer", { + {"everything", 42} + }}, + {"list", {1, 0, 2}}, + {"object", { + {"currency", "USD"}, + {"value", 42.99} + }} +}; +``` + +Note that in all these cases, you never need to "tell" the compiler which JSON value you want to use. If you want to be explicit or express some edge cases, the functions `json::array` and `json::object` will help: + +```cpp +// a way to express the empty array [] +json empty_array_explicit = json::array(); + +// ways to express the empty object {} +json empty_object_implicit = json({}); +json empty_object_explicit = json::object(); + +// a way to express an _array_ of key/value pairs [["currency", "USD"], ["value", 42.99]] +json array_not_object = { json::array({"currency", "USD"}), json::array({"value", 42.99}) }; +``` + + +### Serialization / Deserialization + +You can create an object (deserialization) by appending `_json` to a string literal: + +```cpp +// create object from string literal +json j = "{ \"happy\": true, \"pi\": 3.141 }"_json; + +// or even nicer with a raw string literal +auto j2 = R"( + { + "happy": true, + "pi": 3.141 + } +)"_json; + +// or explicitly +auto j3 = json::parse("{ \"happy\": true, \"pi\": 3.141 }"); +``` + +You can also get a string representation (serialize): + +```cpp +// explicit conversion to string +std::string s = j.dump(); // {\"happy\":true,\"pi\":3.141} + +// serialization with pretty printing +// pass in the amount of spaces to indent +std::cout << j.dump(4) << std::endl; +// { +// "happy": true, +// "pi": 3.141 +// } +``` + +You can also use streams to serialize and deserialize: + +```cpp +// deserialize from standard input +json j; +std::cin >> j; + +// serialize to standard output +std::cout << j; + +// the setw manipulator was overloaded to set the indentation for pretty printing +std::cout << std::setw(4) << j << std::endl; +``` + +These operators work for any subclasses of `std::istream` or `std::ostream`. + +Please note that setting the exception bit for `failbit` is inappropriate for this use case. It will result in program termination due to the `noexcept` specifier in use. + + +### STL-like access + +We designed the JSON class to behave just like an STL container. In fact, it satisfies the [**ReversibleContainer**](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) requirement. + +```cpp +// create an array using push_back +json j; +j.push_back("foo"); +j.push_back(1); +j.push_back(true); + +// iterate the array +for (json::iterator it = j.begin(); it != j.end(); ++it) { + std::cout << *it << '\n'; +} + +// range-based for +for (auto& element : j) { + std::cout << element << '\n'; +} + +// getter/setter +const std::string tmp = j[0]; +j[1] = 42; +bool foo = j.at(2); + +// other stuff +j.size(); // 3 entries +j.empty(); // false +j.type(); // json::value_t::array +j.clear(); // the array is empty again + +// convenience type checkers +j.is_null(); +j.is_boolean(); +j.is_number(); +j.is_object(); +j.is_array(); +j.is_string(); + +// comparison +j == "[\"foo\", 1, true]"_json; // true + +// create an object +json o; +o["foo"] = 23; +o["bar"] = false; +o["baz"] = 3.141; + +// special iterator member functions for objects +for (json::iterator it = o.begin(); it != o.end(); ++it) { + std::cout << it.key() << " : " << it.value() << "\n"; +} + +// find an entry +if (o.find("foo") != o.end()) { + // there is an entry with key "foo" +} + +// or simpler using count() +int foo_present = o.count("foo"); // 1 +int fob_present = o.count("fob"); // 0 + +// delete an entry +o.erase("foo"); +``` + + +### Conversion from STL containers + +Any sequence container (`std::array`, `std::vector`, `std::deque`, `std::forward_list`, `std::list`) whose values can be used to construct JSON types (e.g., integers, floating point numbers, Booleans, string types, or again STL containers described in this section) can be used to create a JSON array. The same holds for similar associative containers (`std::set`, `std::multiset`, `std::unordered_set`, `std::unordered_multiset`), but in these cases the order of the elements of the array depends how the elements are ordered in the respective STL container. + +```cpp +std::vector c_vector {1, 2, 3, 4}; +json j_vec(c_vector); +// [1, 2, 3, 4] + +std::deque c_deque {1.2, 2.3, 3.4, 5.6}; +json j_deque(c_deque); +// [1.2, 2.3, 3.4, 5.6] + +std::list c_list {true, true, false, true}; +json j_list(c_list); +// [true, true, false, true] + +std::forward_list c_flist {12345678909876, 23456789098765, 34567890987654, 45678909876543}; +json j_flist(c_flist); +// [12345678909876, 23456789098765, 34567890987654, 45678909876543] + +std::array c_array {{1, 2, 3, 4}}; +json j_array(c_array); +// [1, 2, 3, 4] + +std::set c_set {"one", "two", "three", "four", "one"}; +json j_set(c_set); // only one entry for "one" is used +// ["four", "one", "three", "two"] + +std::unordered_set c_uset {"one", "two", "three", "four", "one"}; +json j_uset(c_uset); // only one entry for "one" is used +// maybe ["two", "three", "four", "one"] + +std::multiset c_mset {"one", "two", "one", "four"}; +json j_mset(c_mset); // both entries for "one" are used +// maybe ["one", "two", "one", "four"] + +std::unordered_multiset c_umset {"one", "two", "one", "four"}; +json j_umset(c_umset); // both entries for "one" are used +// maybe ["one", "two", "one", "four"] +``` + +Likewise, any associative key-value containers (`std::map`, `std::multimap`, `std::unordered_map`, `std::unordered_multimap`) whose keys can construct an `std::string` and whose values can be used to construct JSON types (see examples above) can be used to to create a JSON object. Note that in case of multimaps only one key is used in the JSON object and the value depends on the internal order of the STL container. + +```cpp +std::map c_map { {"one", 1}, {"two", 2}, {"three", 3} }; +json j_map(c_map); +// {"one": 1, "three": 3, "two": 2 } + +std::unordered_map c_umap { {"one", 1.2}, {"two", 2.3}, {"three", 3.4} }; +json j_umap(c_umap); +// {"one": 1.2, "two": 2.3, "three": 3.4} + +std::multimap c_mmap { {"one", true}, {"two", true}, {"three", false}, {"three", true} }; +json j_mmap(c_mmap); // only one entry for key "three" is used +// maybe {"one": true, "two": true, "three": true} + +std::unordered_multimap c_ummap { {"one", true}, {"two", true}, {"three", false}, {"three", true} }; +json j_ummap(c_ummap); // only one entry for key "three" is used +// maybe {"one": true, "two": true, "three": true} +``` + +### JSON Pointer and JSON Patch + +The library supports **JSON Pointer** ([RFC 6901](https://tools.ietf.org/html/rfc6901)) as alternative means to address structured values. On top of this, **JSON Patch** ([RFC 6902](https://tools.ietf.org/html/rfc6902)) allows to describe differences between two JSON values - effectively allowing patch and diff operations known from Unix. + +```cpp +// a JSON value +json j_original = R"({ + "baz": ["one", "two", "three"], + "foo": "bar" +})"_json; + +// access members with a JSON pointer (RFC 6901) +j_original["/baz/1"_json_pointer]; +// "two" + +// a JSON patch (RFC 6902) +json j_patch = R"([ + { "op": "replace", "path": "/baz", "value": "boo" }, + { "op": "add", "path": "/hello", "value": ["world"] }, + { "op": "remove", "path": "/foo"} +])"_json; + +// apply the patch +json j_result = j_original.patch(j_patch); +// { +// "baz": "boo", +// "hello": ["world"] +// } + +// calculate a JSON patch from two JSON values +json::diff(j_result, j_original); +// [ +// { "op":" replace", "path": "/baz", "value": ["one", "two", "three"] }, +// { "op": "remove","path": "/hello" }, +// { "op": "add", "path": "/foo", "value": "bar" } +// ] +``` + + +### Implicit conversions + +The type of the JSON object is determined automatically by the expression to store. Likewise, the stored value is implicitly converted. + +```cpp +// strings +std::string s1 = "Hello, world!"; +json js = s1; +std::string s2 = js; + +// Booleans +bool b1 = true; +json jb = b1; +bool b2 = jb; + +// numbers +int i = 42; +json jn = i; +double f = jn; + +// etc. +``` + +You can also explicitly ask for the value: + +```cpp +std::string vs = js.get(); +bool vb = jb.get(); +int vi = jn.get(); + +// etc. +``` + + +## Supported compilers + +Though it's 2016 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work: + +- GCC 4.9 - 6.0 (and possibly later) +- Clang 3.4 - 3.9 (and possibly later) +- Microsoft Visual C++ 2015 / Build Tools 14.0.25123.0 (and possibly later) + +I would be happy to learn about other compilers/versions. + +Please note: + +- GCC 4.8 does not work because of two bugs ([55817](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55817) and [57824](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57824)) in the C++11 support. Note there is a [pull request](https://github.com/nlohmann/json/pull/212) to fix some of the issues. +- Android defaults to using very old compilers and C++ libraries. To fix this, add the following to your `Application.mk`. This will switch to the LLVM C++ library, the Clang compiler, and enable C++11 and other features disabled by default. + + ``` + APP_STL := c++_shared + NDK_TOOLCHAIN_VERSION := clang3.6 + APP_CPPFLAGS += -frtti -fexceptions + ``` + + The code compiles successfully with [Android NDK](https://developer.android.com/ndk/index.html?hl=ml), Revision 9 - 11 (and possibly later) and [CrystaX's Android NDK](https://www.crystax.net/en/android/ndk) version 10. + +- For GCC running on MinGW or Android SDK, the error `'to_string' is not a member of 'std'` (or similarly, for `strtod`) may occur. Note this is not an issue with the code, but rather with the compiler itself. On Android, see above to build with a newer environment. For MinGW, please refer to [this site](http://tehsausage.com/mingw-to-string) and [this discussion](https://github.com/nlohmann/json/issues/136) for information on how to fix this bug. For Android NDK using `APP_STL := gnustl_static`, please refer to [this discussion](https://github.com/nlohmann/json/issues/219). + +The following compilers are currently used in continuous integration at [Travis](https://travis-ci.org/nlohmann/json) and [AppVeyor](https://ci.appveyor.com/project/nlohmann/json): + +| Compiler | Operating System | Version String | +|-----------------|------------------------------|----------------| +| GCC 4.9.3 | Ubuntu 14.04.4 LTS | g++-4.9 (Ubuntu 4.9.3-8ubuntu2~14.04) 4.9.3 | +| GCC 5.3.0 | Ubuntu 14.04.4 LTS | g++-5 (Ubuntu 5.3.0-3ubuntu1~14.04) 5.3.0 20151204 | +| GCC 6.1.1 | Ubuntu 14.04.4 LTS | g++-6 (Ubuntu 6.1.1-3ubuntu11~14.04.1) 6.1.1 20160511 | +| Clang 3.6.0 | Ubuntu 14.04.4 LTS | clang version 3.6.0 (tags/RELEASE_360/final) | +| Clang 3.6.1 | Ubuntu 14.04.4 LTS | clang version 3.6.1 (tags/RELEASE_361/final) | +| Clang 3.6.2 | Ubuntu 14.04.4 LTS | clang version 3.6.2 (tags/RELEASE_362/final) | +| Clang 3.7.0 | Ubuntu 14.04.4 LTS | clang version 3.7.0 (tags/RELEASE_370/final) | +| Clang 3.7.1 | Ubuntu 14.04.4 LTS | clang version 3.7.1 (tags/RELEASE_371/final) | +| Clang 3.8.0 | Ubuntu 14.04.4 LTS | clang version 3.8.0 (tags/RELEASE_380/final) | +| Clang 3.8.1 | Ubuntu 14.04.4 LTS | clang version 3.8.1 (tags/RELEASE_381/final) | +| Clang Xcode 6.1 | Darwin Kernel Version 13.4.0 (OSX 10.9.5) | Apple LLVM version 6.0 (clang-600.0.54) (based on LLVM 3.5svn) | +| Clang Xcode 6.2 | Darwin Kernel Version 13.4.0 (OSX 10.9.5) | Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn) | +| Clang Xcode 6.3 | Darwin Kernel Version 14.3.0 (OSX 10.10.3) | Apple LLVM version 6.1.0 (clang-602.0.49) (based on LLVM 3.6.0svn) | +| Clang Xcode 6.4 | Darwin Kernel Version 14.3.0 (OSX 10.10.3) | Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn) | +| Clang Xcode 7.1 | Darwin Kernel Version 14.5.0 (OSX 10.10.5) | Apple LLVM version 7.0.0 (clang-700.1.76) | +| Clang Xcode 7.2 | Darwin Kernel Version 15.0.0 (OSX 10.10.5) | Apple LLVM version 7.0.2 (clang-700.1.81) | +| Clang Xcode 7.3 | Darwin Kernel Version 15.0.0 (OSX 10.10.5) | Apple LLVM version 7.3.0 (clang-703.0.29) | +| Clang Xcode 8.0 | Darwin Kernel Version 15.6.0 (OSX 10.11.6) | Apple LLVM version 8.0.0 (clang-800.0.38) | +| Visual Studio 14 2015 | Windows Server 2012 R2 (x64) | Microsoft (R) Build Engine version 14.0.25123.0 | + + +## License + + + +The class is licensed under the [MIT License](http://opensource.org/licenses/MIT): + +Copyright © 2013-2016 [Niels Lohmann](http://nlohmann.me) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +## Thanks + +I deeply appreciate the help of the following people. + +- [Teemperor](https://github.com/Teemperor) implemented CMake support and lcov integration, realized escape and Unicode handling in the string parser, and fixed the JSON serialization. +- [elliotgoodrich](https://github.com/elliotgoodrich) fixed an issue with double deletion in the iterator classes. +- [kirkshoop](https://github.com/kirkshoop) made the iterators of the class composable to other libraries. +- [wancw](https://github.com/wanwc) fixed a bug that hindered the class to compile with Clang. +- Tomas Åblad found a bug in the iterator implementation. +- [Joshua C. Randall](https://github.com/jrandall) fixed a bug in the floating-point serialization. +- [Aaron Burghardt](https://github.com/aburgh) implemented code to parse streams incrementally. Furthermore, he greatly improved the parser class by allowing the definition of a filter function to discard undesired elements while parsing. +- [Daniel Kopeček](https://github.com/dkopecek) fixed a bug in the compilation with GCC 5.0. +- [Florian Weber](https://github.com/Florianjw) fixed a bug in and improved the performance of the comparison operators. +- [Eric Cornelius](https://github.com/EricMCornelius) pointed out a bug in the handling with NaN and infinity values. He also improved the performance of the string escaping. +- [易思龙](https://github.com/likebeta) implemented a conversion from anonymous enums. +- [kepkin](https://github.com/kepkin) patiently pushed forward the support for Microsoft Visual studio. +- [gregmarr](https://github.com/gregmarr) simplified the implementation of reverse iterators and helped with numerous hints and improvements. +- [Caio Luppi](https://github.com/caiovlp) fixed a bug in the Unicode handling. +- [dariomt](https://github.com/dariomt) fixed some typos in the examples. +- [Daniel Frey](https://github.com/d-frey) cleaned up some pointers and implemented exception-safe memory allocation. +- [Colin Hirsch](https://github.com/ColinH) took care of a small namespace issue. +- [Huu Nguyen](https://github.com/whoshuu) correct a variable name in the documentation. +- [Silverweed](https://github.com/silverweed) overloaded `parse()` to accept an rvalue reference. +- [dariomt](https://github.com/dariomt) fixed a subtlety in MSVC type support and implemented the `get_ref()` function to get a reference to stored values. +- [ZahlGraf](https://github.com/ZahlGraf) added a workaround that allows compilation using Android NDK. +- [whackashoe](https://github.com/whackashoe) replaced a function that was marked as unsafe by Visual Studio. +- [406345](https://github.com/406345) fixed two small warnings. +- [Glen Fernandes](https://github.com/glenfe) noted a potential portability problem in the `has_mapped_type` function. +- [Corbin Hughes](https://github.com/nibroc) fixed some typos in the contribution guidelines. +- [twelsby](https://github.com/twelsby) fixed the array subscript operator, an issue that failed the MSVC build, and floating-point parsing/dumping. He further added support for unsigned integer numbers and implemented better roundtrip support for parsed numbers. +- [Volker Diels-Grabsch](https://github.com/vog) fixed a link in the README file. +- [msm-](https://github.com/msm-) added support for american fuzzy lop. +- [Annihil](https://github.com/Annihil) fixed an example in the README file. +- [Themercee](https://github.com/Themercee) noted a wrong URL in the README file. +- [Lv Zheng](https://github.com/lv-zheng) fixed a namespace issue with `int64_t` and `uint64_t`. +- [abc100m](https://github.com/abc100m) analyzed the issues with GCC 4.8 and proposed a [partial solution](https://github.com/nlohmann/json/pull/212). +- [zewt](https://github.com/zewt) added useful notes to the README file about Android. +- [Róbert Márki](https://github.com/robertmrk) added a fix to use move iterators and improved the integration via CMake. +- [Chris Kitching](https://github.com/ChrisKitching) cleaned up the CMake files. +- [Tom Needham](https://github.com/06needhamt) fixed a subtle bug with MSVC 2015 which was also proposed by [Michael K.](https://github.com/Epidal). +- [Mário Feroldi](https://github.com/thelostt) fixed a small typo. +- [duncanwerner](https://github.com/duncanwerner) found a really embarrassing performance regression in the 2.0.0 release. +- [Damien](https://github.com/dtoma) fixed one of the last conversion warnings. +- [Thomas Braun](https://github.com/t-b) fixed a warning in a test case. +- [Théo DELRIEU](https://github.com/theodelrieu) patiently and constructively oversaw the long way toward [iterator-range parsing](https://github.com/nlohmann/json/issues/290). +- [Stefan](https://github.com/5tefan) fixed a minor issue in the documentation. +- [Vasil Dimov](https://github.com/vasild) fixed the documentation regarding conversions from `std::multiset`. +- [ChristophJud](https://github.com/ChristophJud) overworked the CMake files to ease project inclusion. +- [Vladimir Petrigo](https://github.com/vpetrigo) made a SFINAE hack more readable. +- [Denis Andrejew](https://github.com/seeekr) fixed a grammar issue in the README file. + +Thanks a lot for helping out! + + +## Notes + +- The code contains numerous debug **assertions** which can be switched off by defining the preprocessor macro `NDEBUG`, see the [documentation of `assert`](http://en.cppreference.com/w/cpp/error/assert). In particular, note [`operator[]`](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a2e26bd0b0168abb61f67ad5bcd5b9fa1.html#a2e26bd0b0168abb61f67ad5bcd5b9fa1) implements **unchecked access** for const objects: If the given key is not present, the behavior is undefined (think of a dereferenced null pointer) and yields an [assertion failure](https://github.com/nlohmann/json/issues/289) if assertions are switched on. If you are not sure whether an element in an object exists, use checked access with the [`at()` function](https://nlohmann.github.io/json/classnlohmann_1_1basic__json_a674de1ee73e6bf4843fc5dc1351fb726.html#a674de1ee73e6bf4843fc5dc1351fb726). +- As the exact type of a number is not defined in the [JSON specification](http://rfc7159.net/rfc7159), this library tries to choose the best fitting C++ number type automatically. As a result, the type `double` may be used to store numbers which may yield [**floating-point exceptions**](https://github.com/nlohmann/json/issues/181) in certain rare situations if floating-point exceptions have been unmasked in the calling code. These exceptions are not caused by the library and need to be fixed in the calling code, such as by re-masking the exceptions prior to calling library functions. +- The library supports **Unicode input** as follows: + - Only **UTF-8** encoded input is supported which is the default encoding for JSON according to [RFC 7159](http://rfc7159.net/rfc7159#rfc.section.8.1). + - Other encodings such as Latin-1, UTF-16, or UTF-32 are not supported and will yield parse errors. + - [Unicode noncharacters](http://www.unicode.org/faq/private_use.html#nonchar1) will not be replaced by the library. + - Invalid surrogates (e.g., incomplete pairs such as `\uDEAD`) will yield parse errors. + + +## Execute unit tests + +To compile and run the tests, you need to execute + +```sh +$ make check + +=============================================================================== +All tests passed (8905491 assertions in 36 test cases) +``` + +Alternatively, you can use [CMake](https://cmake.org) and run + +```sh +$ mkdir build +$ cd build +$ cmake .. +$ make +$ ctest +``` + +For more information, have a look at the file [.travis.yml](https://github.com/nlohmann/json/blob/master/.travis.yml). diff --git a/ext/json/json.hpp b/ext/json/json.hpp new file mode 100644 index 0000000..9d48e7a --- /dev/null +++ b/ext/json/json.hpp @@ -0,0 +1,12275 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 2.0.10 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +Copyright (c) 2013-2017 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef NLOHMANN_JSON_HPP +#define NLOHMANN_JSON_HPP + +#include // all_of, for_each, transform +#include // array +#include // assert +#include // isdigit +#include // and, not, or +#include // isfinite, ldexp, signbit +#include // nullptr_t, ptrdiff_t, size_t +#include // int64_t, uint64_t +#include // strtod, strtof, strtold, strtoul +#include // strlen +#include // function, hash, less +#include // initializer_list +#include // setw +#include // istream, ostream +#include // advance, begin, bidirectional_iterator_tag, distance, end, inserter, iterator, iterator_traits, next, random_access_iterator_tag, reverse_iterator +#include // numeric_limits +#include // locale +#include // map +#include // addressof, allocator, allocator_traits, unique_ptr +#include // accumulate +#include // stringstream +#include // domain_error, invalid_argument, out_of_range +#include // getline, stoi, string, to_string +#include // add_pointer, enable_if, is_arithmetic, is_base_of, is_const, is_constructible, is_convertible, is_floating_point, is_integral, is_nothrow_move_assignable, std::is_nothrow_move_constructible, std::is_pointer, std::is_reference, std::is_same, remove_const, remove_pointer, remove_reference +#include // declval, forward, make_pair, move, pair, swap +#include // vector + +// exclude unsupported compilers +#if defined(__clang__) + #define CLANG_VERSION (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) + #if CLANG_VERSION < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#elif defined(__GNUC__) + #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) + #if GCC_VERSION < 40900 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow for portable deprecation warnings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define JSON_DEPRECATED __declspec(deprecated) +#else + #define JSON_DEPRECATED +#endif + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ + + +/*! +@brief unnamed namespace with internal helper functions +@since version 1.0.0 +*/ +namespace +{ +/*! +@brief Helper to determine whether there's a key_type for T. + +Thus helper is used to tell associative containers apart from other containers +such as sequence containers. For instance, `std::map` passes the test as it +contains a `mapped_type`, whereas `std::vector` fails the test. + +@sa http://stackoverflow.com/a/7728728/266378 +@since version 1.0.0, overworked in version 2.0.6 +*/ +template +struct has_mapped_type +{ + private: + template + static int detect(U&&); + + static void detect(...); + public: + static constexpr bool value = + std::is_integral()))>::value; +}; + +} + +/*! +@brief a class to store JSON values + +@tparam ObjectType type for JSON objects (`std::map` by default; will be used +in @ref object_t) +@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used +in @ref array_t) +@tparam StringType type for JSON strings and object keys (`std::string` by +default; will be used in @ref string_t) +@tparam BooleanType type for JSON booleans (`bool` by default; will be used +in @ref boolean_t) +@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by +default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) +@tparam NumberFloatType type for JSON floating-point numbers (`double` by +default; will be used in @ref number_float_t) +@tparam AllocatorType type of the allocator to use (`std::allocator` by +default) + +@requirement The class satisfies the following concept requirements: +- Basic + - [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible): + JSON values can be default constructed. The result will be a JSON null value. + - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): + A JSON value can be constructed from an rvalue argument. + - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): + A JSON value can be copy-constructed from an lvalue expression. + - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): + A JSON value van be assigned from an rvalue argument. + - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): + A JSON value can be copy-assigned from an lvalue expression. + - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible): + JSON values can be destructed. +- Layout + - [StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType): + JSON values have + [standard layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout): + All non-static data members are private and standard layout types, the class + has no virtual functions or (virtual) base classes. +- Library-wide + - [EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable): + JSON values can be compared with `==`, see @ref + operator==(const_reference,const_reference). + - [LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable): + JSON values can be compared with `<`, see @ref + operator<(const_reference,const_reference). + - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable): + Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of + other compatible types, using unqualified function call @ref swap(). + - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer): + JSON values can be compared against `std::nullptr_t` objects which are used + to model the `null` value. +- Container + - [Container](http://en.cppreference.com/w/cpp/concept/Container): + JSON values can be used like STL containers and provide iterator access. + - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer); + JSON values can be used like STL containers and provide reverse iterator + access. + +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@internal +@note ObjectType trick from http://stackoverflow.com/a/9860911 +@endinternal + +@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange +Format](http://rfc7159.net/rfc7159) + +@since version 1.0.0 + +@nosubgrouping +*/ +template < + template class ObjectType = std::map, + template class ArrayType = std::vector, + class StringType = std::string, + class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator + > +class basic_json +{ + private: + /// workaround type for MSVC + using basic_json_t = basic_json; + + public: + // forward declarations + template class iter_impl; + template class json_reverse_iterator; + class json_pointer; + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType; + + /// the type of an element pointer + using pointer = typename std::allocator_traits::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits::const_pointer; + + /// an iterator for a basic_json container + using iterator = iter_impl; + /// a const iterator for a basic_json container + using const_iterator = iter_impl; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator; + + /// @} + + + /*! + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() + { + return allocator_type(); + } + + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + + /*! + @brief a type for an object + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: + > An object is an unordered collection of zero or more name/value pairs, + > where a name is a string and a value is a string, number, boolean, null, + > object, or array. + + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) + + #### Default type + + With the default values for @a ObjectType (`std::map`), @a StringType + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: + + @code {.cpp} + std::map< + std::string, // key_type + basic_json, // value_type + std::less, // key_compare + std::allocator> // allocator_type + > + @endcode + + #### Behavior + + The choice of @a object_t influences the behavior of the JSON class. With + the default type, objects have the following behavior: + + - When all names are unique, objects will be interoperable in the sense + that all software implementations receiving that object will agree on + the name-value mappings. + - When the names within an object are not unique, later stored name/value + pairs overwrite previously stored name/value pairs, leaving the used + names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will + be treated as equal and both stored as `{"key": 1}`. + - Internally, name/value pairs are stored in lexicographical order of the + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. + - When comparing objects, the order of the name/value pairs is irrelevant. + This makes objects interoperable in the sense that they will not be + affected by these differences. For instance, `{"b": 1, "a": 2}` and + `{"a": 2, "b": 1}` will be treated as equal. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the object's limit of nesting is not constraint explicitly. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. + + #### Storage + + Objects are stored as pointers in a @ref basic_json type. That is, for any + access to object values, a pointer of type `object_t*` must be + dereferenced. + + @sa @ref array_t -- type for an array value + + @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. + */ + using object_t = ObjectType, + AllocatorType>>; + + /*! + @brief a type for an array + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: + > An array is an ordered sequence of zero or more values. + + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) + + #### Default type + + With the default values for @a ArrayType (`std::vector`) and @a + AllocatorType (`std::allocator`), the default value for @a array_t is: + + @code {.cpp} + std::vector< + basic_json, // value_type + std::allocator // allocator_type + > + @endcode + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the array's limit of nesting is not constraint explicitly. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. + + #### Storage + + Arrays are stored as pointers in a @ref basic_json type. That is, for any + access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa @ref object_t -- type for an object value + + @since version 1.0.0 + */ + using array_t = ArrayType>; + + /*! + @brief a type for a string + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: + > A string is a sequence of zero or more Unicode characters. + + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. + + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. + + #### Default type + + With the default values for @a StringType (`std::string`), the default + value for @a string_t is: + + @code {.cpp} + std::string + @endcode + + #### String comparison + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > Software implementations are typically required to test names of object + > members for equality. Implementations that transform the textual + > representation into sequences of Unicode code units and then perform the + > comparison numerically, code unit by code unit, are interoperable in the + > sense that implementations will agree in all cases on equality or + > inequality of two strings. For example, implementations that compare + > strings with escaped characters unconverted may incorrectly find that + > `"a\\b"` and `"a\u005Cb"` are not equal. + + This implementation is interoperable as it does compare strings code unit + by code unit. + + #### Storage + + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be + dereferenced. + + @since version 1.0.0 + */ + using string_t = StringType; + + /*! + @brief a type for a boolean + + [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a + type which differentiates the two literals `true` and `false`. + + To store objects in C++, a type is defined by the template parameter @a + BooleanType which chooses the type to use. + + #### Default type + + With the default values for @a BooleanType (`bool`), the default value for + @a boolean_t is: + + @code {.cpp} + bool + @endcode + + #### Storage + + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 + */ + using boolean_t = BooleanType; + + /*! + @brief a type for a number (integer) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store integer numbers in C++, a type is defined by the template + parameter @a NumberIntegerType which chooses the type to use. + + #### Default type + + With the default values for @a NumberIntegerType (`int64_t`), the default + value for @a number_integer_t is: + + @code {.cpp} + int64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `9223372036854775807` (INT64_MAX) and the minimal integer number + that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange of the exactly supported range [INT64_MIN, + INT64_MAX], this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_integer_t = NumberIntegerType; + + /*! + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], + this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + + /*! + @brief a type for a number (floating-point) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store floating-point numbers in C++, a type is defined by the template + parameter @a NumberFloatType which chooses the type to use. + + #### Default type + + With the default values for @a NumberFloatType (`double`), the default + value for @a number_float_t is: + + @code {.cpp} + double + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ + floating-point literal `01.2` will be serialized to `1.2`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > This specification allows implementations to set limits on the range and + > precision of numbers accepted. Since software that implements IEEE + > 754-2008 binary64 (double precision) numbers is generally available and + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected + > precision. + + This implementation does exactly follow this approach, as it uses double + precision floating-point numbers. Note values smaller than + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` + will be stored as NaN internally and be serialized to `null`. + + #### Storage + + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa @ref number_integer_t -- type for number values (integer) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_float_t = NumberFloatType; + + /// @} + + + /////////////////////////// + // JSON type enumeration // + /////////////////////////// + + /*! + @brief the JSON type enumeration + + This enumeration collects the different JSON types. It is internally used + to distinguish the stored values, and the functions @ref is_null(), @ref + is_object(), @ref is_array(), @ref is_string(), @ref is_boolean(), @ref + is_number() (with @ref is_number_integer(), @ref is_number_unsigned(), and + @ref is_number_float()), @ref is_discarded(), @ref is_primitive(), and + @ref is_structured() rely on it. + + @note There are three enumeration entries (number_integer, + number_unsigned, and number_float), because the library distinguishes + these three types for numbers: @ref number_unsigned_t is used for unsigned + integers, @ref number_integer_t is used for signed integers, and @ref + number_float_t is used for floating-point numbers or to approximate + integers which do not fit in the limits of their respective type. + + @sa @ref basic_json(const value_t value_type) -- create a JSON value with + the default value for a given type + + @since version 1.0.0 + */ + enum class value_t : uint8_t + { + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + discarded ///< discarded by the the parser callback function + }; + + + private: + + /// helper for exception-safe object creation + template + static T* create(Args&& ... args) + { + AllocatorType alloc; + auto deleter = [&](T * object) + { + alloc.deallocate(object, 1); + }; + std::unique_ptr object(alloc.allocate(1), deleter); + alloc.construct(object.get(), std::forward(args)...); + assert(object.get() != nullptr); + return object.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create(); + break; + } + + case value_t::array: + { + array = create(); + break; + } + + case value_t::string: + { + string = create(""); + break; + } + + case value_t::boolean: + { + boolean = boolean_t(false); + break; + } + + case value_t::number_integer: + { + number_integer = number_integer_t(0); + break; + } + + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } + + case value_t::number_float: + { + number_float = number_float_t(0.0); + break; + } + + case value_t::null: + { + break; + } + + default: + { + if (t == value_t::null) + { + throw std::domain_error("961c151d2e87f2686a955a9be24d316f1362bf21 2.0.10"); // LCOV_EXCL_LINE + } + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) + { + string = create(value); + } + + /// constructor for objects + json_value(const object_t& value) + { + object = create(value); + } + + /// constructor for arrays + json_value(const array_t& value) + { + array = create(value); + } + }; + + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + */ + void assert_invariant() const + { + assert(m_type != value_t::object or m_value.object != nullptr); + assert(m_type != value_t::array or m_value.array != nullptr); + assert(m_type != value_t::string or m_value.string != nullptr); + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /*! + @brief JSON callback events + + This enumeration lists the parser events that can trigger calling a + callback function of type @ref parser_callback_t during parsing. + + @image html callback_events.png "Example when certain parse events are triggered" + + @since version 1.0.0 + */ + enum class parse_event_t : uint8_t + { + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value + }; + + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse(std::istream&, const + parser_callback_t) or @ref parse(const CharT, const parser_callback_t), + it is called on certain events (passed as @ref parse_event_t via parameter + @a event) with a set recursion depth @a depth and context JSON value + @a parsed. The return value of the callback function is a boolean + indicating whether the element that emitted the callback shall be kept or + not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa @ref parse(std::istream&, parser_callback_t) or + @ref parse(const CharT, const parser_callback_t) for examples + + @since version 1.0.0 + */ + using parser_callback_t = std::function; + + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /*! + @brief create an empty value with a given type + + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @param[in] value_type the type of the value to create + + @complexity Constant. + + @throw std::bad_alloc if allocation for object, array, or string value + fails + + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @sa @ref basic_json(std::nullptr_t) -- create a `null` value + @sa @ref basic_json(boolean_t value) -- create a boolean value + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const object_t&) -- create a object value + @sa @ref basic_json(const array_t&) -- create a array value + @sa @ref basic_json(const number_float_t) -- create a number + (floating-point) value + @sa @ref basic_json(const number_integer_t) -- create a number (integer) + value + @sa @ref basic_json(const number_unsigned_t) -- create a number (unsigned) + value + + @since version 1.0.0 + */ + basic_json(const value_t value_type) + : m_type(value_type), m_value(value_type) + { + assert_invariant(); + } + + /*! + @brief create a null object + + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} + + @since version 1.0.0 + */ + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) + { + assert_invariant(); + } + + /*! + @brief create an object (explicit) + + Create an object JSON value with a given content. + + @param[in] val a value for the object + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for object value fails + + @liveexample{The following code shows the constructor with an @ref + object_t parameter.,basic_json__object_t} + + @sa @ref basic_json(const CompatibleObjectType&) -- create an object value + from a compatible STL container + + @since version 1.0.0 + */ + basic_json(const object_t& val) + : m_type(value_t::object), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an object (implicit) + + Create an object JSON value with a given content. This constructor allows + any type @a CompatibleObjectType that can be used to construct values of + type @ref object_t. + + @tparam CompatibleObjectType An object type whose `key_type` and + `value_type` is compatible to @ref object_t. Examples include `std::map`, + `std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with + a `key_type` of `std::string`, and a `value_type` from which a @ref + basic_json value can be constructed. + + @param[in] val a value for the object + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for object value fails + + @liveexample{The following code shows the constructor with several + compatible object type parameters.,basic_json__CompatibleObjectType} + + @sa @ref basic_json(const object_t&) -- create an object value + + @since version 1.0.0 + */ + template::value and + std::is_constructible::value, int>::type = 0> + basic_json(const CompatibleObjectType& val) + : m_type(value_t::object) + { + using std::begin; + using std::end; + m_value.object = create(begin(val), end(val)); + assert_invariant(); + } + + /*! + @brief create an array (explicit) + + Create an array JSON value with a given content. + + @param[in] val a value for the array + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for array value fails + + @liveexample{The following code shows the constructor with an @ref array_t + parameter.,basic_json__array_t} + + @sa @ref basic_json(const CompatibleArrayType&) -- create an array value + from a compatible STL containers + + @since version 1.0.0 + */ + basic_json(const array_t& val) + : m_type(value_t::array), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an array (implicit) + + Create an array JSON value with a given content. This constructor allows + any type @a CompatibleArrayType that can be used to construct values of + type @ref array_t. + + @tparam CompatibleArrayType An object type whose `value_type` is + compatible to @ref array_t. Examples include `std::vector`, `std::deque`, + `std::list`, `std::forward_list`, `std::array`, `std::set`, + `std::unordered_set`, `std::multiset`, and `unordered_multiset` with a + `value_type` from which a @ref basic_json value can be constructed. + + @param[in] val a value for the array + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for array value fails + + @liveexample{The following code shows the constructor with several + compatible array type parameters.,basic_json__CompatibleArrayType} + + @sa @ref basic_json(const array_t&) -- create an array value + + @since version 1.0.0 + */ + template::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + std::is_constructible::value, int>::type = 0> + basic_json(const CompatibleArrayType& val) + : m_type(value_t::array) + { + using std::begin; + using std::end; + m_value.array = create(begin(val), end(val)); + assert_invariant(); + } + + /*! + @brief create a string (explicit) + + Create an string JSON value with a given content. + + @param[in] val a value for the string + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for string value fails + + @liveexample{The following code shows the constructor with an @ref + string_t parameter.,basic_json__string_t} + + @sa @ref basic_json(const typename string_t::value_type*) -- create a + string value from a character pointer + @sa @ref basic_json(const CompatibleStringType&) -- create a string value + from a compatible string container + + @since version 1.0.0 + */ + basic_json(const string_t& val) + : m_type(value_t::string), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create a string (explicit) + + Create a string JSON value with a given content. + + @param[in] val a literal value for the string + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for string value fails + + @liveexample{The following code shows the constructor with string literal + parameter.,basic_json__string_t_value_type} + + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const CompatibleStringType&) -- create a string value + from a compatible string container + + @since version 1.0.0 + */ + basic_json(const typename string_t::value_type* val) + : basic_json(string_t(val)) + { + assert_invariant(); + } + + /*! + @brief create a string (implicit) + + Create a string JSON value with a given content. + + @param[in] val a value for the string + + @tparam CompatibleStringType an string type which is compatible to @ref + string_t, for instance `std::string`. + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for string value fails + + @liveexample{The following code shows the construction of a string value + from a compatible type.,basic_json__CompatibleStringType} + + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const typename string_t::value_type*) -- create a + string value from a character pointer + + @since version 1.0.0 + */ + template::value, int>::type = 0> + basic_json(const CompatibleStringType& val) + : basic_json(string_t(val)) + { + assert_invariant(); + } + + /*! + @brief create a boolean (explicit) + + Creates a JSON boolean type from a given value. + + @param[in] val a boolean value to store + + @complexity Constant. + + @liveexample{The example below demonstrates boolean + values.,basic_json__boolean_t} + + @since version 1.0.0 + */ + basic_json(boolean_t val) noexcept + : m_type(value_t::boolean), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an integer number (explicit) + + Create an integer number JSON value with a given content. + + @tparam T A helper type to remove this function via SFINAE in case @ref + number_integer_t is the same as `int`. In this case, this constructor + would have the same signature as @ref basic_json(const int value). Note + the helper type @a T is not visible in this constructor's interface. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @liveexample{The example below shows the construction of an integer + number value.,basic_json__number_integer_t} + + @sa @ref basic_json(const int) -- create a number value (integer) + @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number + value (integer) from a compatible number type + + @since version 1.0.0 + */ + template::value) and + std::is_same::value, int>::type = 0> + basic_json(const number_integer_t val) noexcept + : m_type(value_t::number_integer), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an integer number from an enum type (explicit) + + Create an integer number JSON value with a given content. + + @param[in] val an integer to create a JSON number from + + @note This constructor allows to pass enums directly to a constructor. As + C++ has no way of specifying the type of an anonymous enum explicitly, we + can only rely on the fact that such values implicitly convert to int. As + int may already be the same type of number_integer_t, we may need to + switch off the constructor @ref basic_json(const number_integer_t). + + @complexity Constant. + + @liveexample{The example below shows the construction of an integer + number value from an anonymous enum.,basic_json__const_int} + + @sa @ref basic_json(const number_integer_t) -- create a number value + (integer) + @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number + value (integer) from a compatible number type + + @since version 1.0.0 + */ + basic_json(const int val) noexcept + : m_type(value_t::number_integer), + m_value(static_cast(val)) + { + assert_invariant(); + } + + /*! + @brief create an integer number (implicit) + + Create an integer number JSON value with a given content. This constructor + allows any type @a CompatibleNumberIntegerType that can be used to + construct values of type @ref number_integer_t. + + @tparam CompatibleNumberIntegerType An integer type which is compatible to + @ref number_integer_t. Examples include the types `int`, `int32_t`, + `long`, and `short`. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @liveexample{The example below shows the construction of several integer + number values from compatible + types.,basic_json__CompatibleIntegerNumberType} + + @sa @ref basic_json(const number_integer_t) -- create a number value + (integer) + @sa @ref basic_json(const int) -- create a number value (integer) + + @since version 1.0.0 + */ + template::value and + std::numeric_limits::is_integer and + std::numeric_limits::is_signed, + CompatibleNumberIntegerType>::type = 0> + basic_json(const CompatibleNumberIntegerType val) noexcept + : m_type(value_t::number_integer), + m_value(static_cast(val)) + { + assert_invariant(); + } + + /*! + @brief create an unsigned integer number (explicit) + + Create an unsigned integer number JSON value with a given content. + + @tparam T helper type to compare number_unsigned_t and unsigned int (not + visible in) the interface. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @sa @ref basic_json(const CompatibleNumberUnsignedType) -- create a number + value (unsigned integer) from a compatible number type + + @since version 2.0.0 + */ + template::value) and + std::is_same::value, int>::type = 0> + basic_json(const number_unsigned_t val) noexcept + : m_type(value_t::number_unsigned), m_value(val) + { + assert_invariant(); + } + + /*! + @brief create an unsigned number (implicit) + + Create an unsigned number JSON value with a given content. This + constructor allows any type @a CompatibleNumberUnsignedType that can be + used to construct values of type @ref number_unsigned_t. + + @tparam CompatibleNumberUnsignedType An integer type which is compatible + to @ref number_unsigned_t. Examples may include the types `unsigned int`, + `uint32_t`, or `unsigned short`. + + @param[in] val an unsigned integer to create a JSON number from + + @complexity Constant. + + @sa @ref basic_json(const number_unsigned_t) -- create a number value + (unsigned) + + @since version 2.0.0 + */ + template::value and + std::numeric_limits::is_integer and + not std::numeric_limits::is_signed, + CompatibleNumberUnsignedType>::type = 0> + basic_json(const CompatibleNumberUnsignedType val) noexcept + : m_type(value_t::number_unsigned), + m_value(static_cast(val)) + { + assert_invariant(); + } + + /*! + @brief create a floating-point number (explicit) + + Create a floating-point number JSON value with a given content. + + @param[in] val a floating-point value to create a JSON number from + + @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 + disallows NaN values: + > Numeric values that cannot be represented in the grammar below (such as + > Infinity and NaN) are not permitted. + In case the parameter @a val is not a number, a JSON null value is created + instead. + + @complexity Constant. + + @liveexample{The following example creates several floating-point + values.,basic_json__number_float_t} + + @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number + value (floating-point) from a compatible number type + + @since version 1.0.0 + */ + basic_json(const number_float_t val) noexcept + : m_type(value_t::number_float), m_value(val) + { + // replace infinity and NAN by null + if (not std::isfinite(val)) + { + m_type = value_t::null; + m_value = json_value(); + } + + assert_invariant(); + } + + /*! + @brief create an floating-point number (implicit) + + Create an floating-point number JSON value with a given content. This + constructor allows any type @a CompatibleNumberFloatType that can be used + to construct values of type @ref number_float_t. + + @tparam CompatibleNumberFloatType A floating-point type which is + compatible to @ref number_float_t. Examples may include the types `float` + or `double`. + + @param[in] val a floating-point to create a JSON number from + + @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 + disallows NaN values: + > Numeric values that cannot be represented in the grammar below (such as + > Infinity and NaN) are not permitted. + In case the parameter @a val is not a number, a JSON null value is + created instead. + + @complexity Constant. + + @liveexample{The example below shows the construction of several + floating-point number values from compatible + types.,basic_json__CompatibleNumberFloatType} + + @sa @ref basic_json(const number_float_t) -- create a number value + (floating-point) + + @since version 1.0.0 + */ + template::value and + std::is_floating_point::value>::type> + basic_json(const CompatibleNumberFloatType val) noexcept + : basic_json(number_float_t(val)) + { + assert_invariant(); + } + + /*! + @brief create a container (array or object) from an initializer list + + Creates a JSON value of type array or object from the passed initializer + list @a init. In case @a type_deduction is `true` (default), the type of + the JSON value to be created is deducted from the initializer list @a init + according to the following rules: + + 1. If the list is empty, an empty JSON object value `{}` is created. + 2. If the list consists of pairs whose first element is a string, a JSON + object value is created where the first elements of the pairs are + treated as keys and the second elements are as values. + 3. In all other cases, an array is created. + + The rules aim to create the best fit between a C++ initializer list and + JSON values. The rationale is as follows: + + 1. The empty initializer list is written as `{}` which is exactly an empty + JSON object. + 2. C++ has now way of describing mapped types other than to list a list of + pairs. As JSON requires that keys must be of type string, rule 2 is the + weakest constraint one can pose on initializer lists to interpret them + as an object. + 3. In all other cases, the initializer list could not be interpreted as + JSON object type, so interpreting it as JSON array type is safe. + + With the rules described above, the following JSON values cannot be + expressed by an initializer list: + + - the empty array (`[]`): use @ref array(std::initializer_list) + with an empty initializer list in this case + - arrays whose elements satisfy rule 2: use @ref + array(std::initializer_list) with the same initializer list + in this case + + @note When used without parentheses around an empty initializer list, @ref + basic_json() is called instead of this function, yielding the JSON null + value. + + @param[in] init initializer list with JSON values + + @param[in] type_deduction internal parameter; when set to `true`, the type + of the JSON value is deducted from the initializer list @a init; when set + to `false`, the type provided via @a manual_type is forced. This mode is + used by the functions @ref array(std::initializer_list) and + @ref object(std::initializer_list). + + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref + value_t::array and @ref value_t::object are valid); when @a type_deduction + is set to `true`, this parameter has no effect + + @throw std::domain_error if @a type_deduction is `false`, @a manual_type + is `value_t::object`, but @a init contains an element which is not a pair + whose first element is a string; example: `"cannot create object from + initializer list"` + + @complexity Linear in the size of the initializer list @a init. + + @liveexample{The example below shows how JSON values are created from + initializer lists.,basic_json__list_init_t} + + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + basic_json(std::initializer_list init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const basic_json & element) + { + return element.is_array() and element.size() == 2 and element[0].is_string(); + }); + + // adjust type if type deduction is not wanted + if (not type_deduction) + { + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if object is wanted but impossible, throw an exception + if (manual_type == value_t::object and not is_an_object) + { + throw std::domain_error("cannot create object from initializer list"); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create object + m_type = value_t::object; + m_value = value_t::object; + + std::for_each(init.begin(), init.end(), [this](const basic_json & element) + { + m_value.object->emplace(*(element[0].m_value.string), element[1]); + }); + } + else + { + // the initializer list describes an array -> create array + m_type = value_t::array; + m_value.array = create(init); + } + + assert_invariant(); + } + + /*! + @brief explicitly create an array from an initializer list + + Creates a JSON array value from a given initializer list. That is, given a + list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the + initializer list is empty, the empty array `[]` is created. + + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref + basic_json(std::initializer_list, bool, value_t)). These cases + are: + 1. creating an array whose elements are all pairs whose first element is a + string -- in this case, the initializer list constructor would create an + object, taking the first elements as keys + 2. creating an empty array -- passing the empty initializer list to the + initializer list constructor yields an empty object + + @param[in] init initializer list with JSON values to create an array from + (optional) + + @return JSON array value + + @complexity Linear in the size of @a init. + + @liveexample{The following code shows an example for the `array` + function.,array} + + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + static basic_json array(std::initializer_list init = + std::initializer_list()) + { + return basic_json(init, false, value_t::array); + } + + /*! + @brief explicitly create an object from an initializer list + + Creates a JSON object value from a given initializer list. The initializer + lists elements must be pairs, and their first elements must be strings. If + the initializer list is empty, the empty object `{}` is created. + + @note This function is only added for symmetry reasons. In contrast to the + related function @ref array(std::initializer_list), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor @ref basic_json(std::initializer_list, bool, + value_t). + + @param[in] init initializer list to create an object from (optional) + + @return JSON object value + + @throw std::domain_error if @a init is not a pair whose first elements are + strings; thrown by + @ref basic_json(std::initializer_list, bool, value_t) + + @complexity Linear in the size of @a init. + + @liveexample{The following code shows an example for the `object` + function.,object} + + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + + @since version 1.0.0 + */ + static basic_json object(std::initializer_list init = + std::initializer_list()) + { + return basic_json(init, false, value_t::object); + } + + /*! + @brief construct an array with count copies of given value + + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. As postcondition, + `std::distance(begin(),end()) == cnt` holds. + + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy + + @complexity Linear in @a cnt. + + @liveexample{The following code shows examples for the @ref + basic_json(size_type\, const basic_json&) + constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 + */ + basic_json(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create(cnt, val); + assert_invariant(); + } + + /*! + @brief construct a JSON container given an iterator range + + Constructs the JSON value with the contents of the range `[first, last)`. + The semantics depends on the different types a JSON value can have: + - In case of primitive types (number, boolean, or string), @a first must + be `begin()` and @a last must be `end()`. In this case, the value is + copied. Otherwise, std::out_of_range is thrown. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector`. + - In case of a null type, std::domain_error is thrown. + + @tparam InputIT an input iterator type (@ref iterator or @ref + const_iterator) + + @param[in] first begin of the range to copy from (included) + @param[in] last end of the range to copy from (excluded) + + @pre Iterators @a first and @a last must be initialized. **This + precondition is enforced with an assertion.** + + @throw std::domain_error if iterators are not compatible; that is, do not + belong to the same JSON value; example: `"iterators are not compatible"` + @throw std::out_of_range if iterators are for a primitive type (number, + boolean, or string) where an out of range error can be detected easily; + example: `"iterators out of range"` + @throw std::bad_alloc if allocation for object, array, or string fails + @throw std::domain_error if called with a null value; example: `"cannot + use construct with iterators from null"` + + @complexity Linear in distance between @a first and @a last. + + @liveexample{The example below shows several ways to create JSON values by + specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type = 0> + basic_json(InputIT first, InputIT last) + { + assert(first.m_object != nullptr); + assert(last.m_object != nullptr); + + // make sure iterator fits the current value + if (first.m_object != last.m_object) + { + throw std::domain_error("iterators are not compatible"); + } + + // copy type from first iterator + m_type = first.m_object->m_type; + + // check if iterator range is complete for primitive values + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) + { + throw std::out_of_range("iterators out of range"); + } + break; + } + + default: + { + break; + } + } + + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value.number_float = first.m_object->m_value.number_float; + break; + } + + case value_t::boolean: + { + m_value.boolean = first.m_object->m_value.boolean; + break; + } + + case value_t::string: + { + m_value = *first.m_object->m_value.string; + break; + } + + case value_t::object: + { + m_value.object = create(first.m_it.object_iterator, last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_value.array = create(first.m_it.array_iterator, last.m_it.array_iterator); + break; + } + + default: + { + throw std::domain_error("cannot use construct with iterators from " + first.m_object->type_name()); + } + } + + assert_invariant(); + } + + /*! + @brief construct a JSON value given an input stream + + @param[in,out] i stream to read a serialized JSON value from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @deprecated This constructor is deprecated and will be removed in version + 3.0.0 to unify the interface of the library. Deserialization will be + done by stream operators or by calling one of the `parse` functions, + e.g. @ref parse(std::istream&, const parser_callback_t). That is, calls + like `json j(i);` for an input stream @a i need to be replaced by + `json j = json::parse(i);`. See the example below. + + @liveexample{The example below demonstrates constructing a JSON value from + a `std::stringstream` with and without callback + function.,basic_json__istream} + + @since version 2.0.0, deprecated in version 2.0.3, to be removed in + version 3.0.0 + */ + JSON_DEPRECATED + explicit basic_json(std::istream& i, const parser_callback_t cb = nullptr) + { + *this = parser(i, cb).parse(); + assert_invariant(); + } + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + /*! + @brief copy constructor + + Creates a copy of a given JSON value. + + @param[in] other the JSON value to copy + + @complexity Linear in the size of @a other. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - As postcondition, it holds: `other == basic_json(other)`. + + @throw std::bad_alloc if allocation for object, array, or string fails. + + @liveexample{The following code shows an example for the copy + constructor.,basic_json__basic_json} + + @since version 1.0.0 + */ + basic_json(const basic_json& other) + : m_type(other.m_type) + { + // check of passed value is valid + other.assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + m_value = *other.m_value.object; + break; + } + + case value_t::array: + { + m_value = *other.m_value.array; + break; + } + + case value_t::string: + { + m_value = *other.m_value.string; + break; + } + + case value_t::boolean: + { + m_value = other.m_value.boolean; + break; + } + + case value_t::number_integer: + { + m_value = other.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value = other.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value = other.m_value.number_float; + break; + } + + default: + { + break; + } + } + + assert_invariant(); + } + + /*! + @brief move constructor + + Move constructor. Constructs a JSON value with the contents of the given + value @a other using move semantics. It "steals" the resources from @a + other and leaves it as JSON null value. + + @param[in,out] other value to move to this object + + @post @a other is a JSON null value + + @complexity Constant. + + @liveexample{The code below shows the move constructor explicitly called + via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 + */ + basic_json(basic_json&& other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) + { + // check that passed value is valid + other.assert_invariant(); + + // invalidate payload + other.m_type = value_t::null; + other.m_value = {}; + + assert_invariant(); + } + + /*! + @brief copy assignment + + Copy assignment operator. Copies a JSON value via the "copy and swap" + strategy: It is expressed in terms of the copy constructor, destructor, + and the swap() member function. + + @param[in] other value to copy from + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + + @liveexample{The code below shows and example for the copy assignment. It + creates a copy of value `a` which is then swapped with `b`. Finally\, the + copy of `a` (which is the null value after the swap) is + destroyed.,basic_json__copyassignment} + + @since version 1.0.0 + */ + reference& operator=(basic_json other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + // check that passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + + assert_invariant(); + return *this; + } + + /*! + @brief destructor + + Destroys the JSON value and frees all allocated memory. + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 + */ + ~basic_json() + { + assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + AllocatorType alloc; + alloc.destroy(m_value.object); + alloc.deallocate(m_value.object, 1); + break; + } + + case value_t::array: + { + AllocatorType alloc; + alloc.destroy(m_value.array); + alloc.deallocate(m_value.array, 1); + break; + } + + case value_t::string: + { + AllocatorType alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + break; + } + + default: + { + // all other types need no specific destructor + break; + } + } + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /*! + @brief serialization + + Serialization function for JSON values. The function tries to mimic + Python's `json.dumps()` function, and currently supports its @a indent + parameter. + + @param[in] indent If indent is nonnegative, then array elements and object + members will be pretty-printed with that indent level. An indent level of + `0` will only insert newlines. `-1` (the default) selects the most compact + representation. + + @return string containing the serialization of the JSON value + + @complexity Linear. + + @liveexample{The following example shows the effect of different @a indent + parameters to the result of the serialization.,dump} + + @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0 + */ + string_t dump(const int indent = -1) const + { + std::stringstream ss; + // fix locale problems + ss.imbue(std::locale::classic()); + + // 6, 15 or 16 digits of precision allows round-trip IEEE 754 + // string->float->string, string->double->string or string->long + // double->string; to be safe, we read this value from + // std::numeric_limits::digits10 + ss.precision(std::numeric_limits::digits10); + + if (indent >= 0) + { + dump(ss, true, static_cast(indent)); + } + else + { + dump(ss, false, 0); + } + + return ss.str(); + } + + /*! + @brief return the type of the JSON value (explicit) + + Return the type of the JSON value as a value from the @ref value_t + enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `type()` for all JSON + types.,type} + + @since version 1.0.0 + */ + constexpr value_t type() const noexcept + { + return m_type; + } + + /*! + @brief return whether type is primitive + + This function returns true iff the JSON type is primitive (string, number, + boolean, or null). + + @return `true` if type is primitive (string, number, boolean, or null), + `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_primitive()` for all JSON + types.,is_primitive} + + @sa @ref is_structured() -- returns whether JSON value is structured + @sa @ref is_null() -- returns whether JSON value is `null` + @sa @ref is_string() -- returns whether JSON value is a string + @sa @ref is_boolean() -- returns whether JSON value is a boolean + @sa @ref is_number() -- returns whether JSON value is a number + + @since version 1.0.0 + */ + constexpr bool is_primitive() const noexcept + { + return is_null() or is_string() or is_boolean() or is_number(); + } + + /*! + @brief return whether type is structured + + This function returns true iff the JSON type is structured (array or + object). + + @return `true` if type is structured (array or object), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_structured()` for all JSON + types.,is_structured} + + @sa @ref is_primitive() -- returns whether value is primitive + @sa @ref is_array() -- returns whether value is an array + @sa @ref is_object() -- returns whether value is an object + + @since version 1.0.0 + */ + constexpr bool is_structured() const noexcept + { + return is_array() or is_object(); + } + + /*! + @brief return whether value is null + + This function returns true iff the JSON value is null. + + @return `true` if type is null, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_null()` for all JSON + types.,is_null} + + @since version 1.0.0 + */ + constexpr bool is_null() const noexcept + { + return m_type == value_t::null; + } + + /*! + @brief return whether value is a boolean + + This function returns true iff the JSON value is a boolean. + + @return `true` if type is boolean, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_boolean()` for all JSON + types.,is_boolean} + + @since version 1.0.0 + */ + constexpr bool is_boolean() const noexcept + { + return m_type == value_t::boolean; + } + + /*! + @brief return whether value is a number + + This function returns true iff the JSON value is a number. This includes + both integer and floating-point values. + + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number()` for all JSON + types.,is_number} + + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number() const noexcept + { + return is_number_integer() or is_number_float(); + } + + /*! + @brief return whether value is an integer number + + This function returns true iff the JSON value is an integer or unsigned + integer number. This excludes floating-point values. + + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_integer()` for all + JSON types.,is_number_integer} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number_integer() const noexcept + { + return m_type == value_t::number_integer or m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true iff the JSON value is an unsigned integer + number. This excludes floating-point and (signed) integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_unsigned()` for all + JSON types.,is_number_unsigned} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + constexpr bool is_number_unsigned() const noexcept + { + return m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is a floating-point number + + This function returns true iff the JSON value is a floating-point number. + This excludes integer and unsigned integer values. + + @return `true` if type is a floating-point number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_float()` for all + JSON types.,is_number_float} + + @sa @ref is_number() -- check if value is number + @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + + @since version 1.0.0 + */ + constexpr bool is_number_float() const noexcept + { + return m_type == value_t::number_float; + } + + /*! + @brief return whether value is an object + + This function returns true iff the JSON value is an object. + + @return `true` if type is object, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_object()` for all JSON + types.,is_object} + + @since version 1.0.0 + */ + constexpr bool is_object() const noexcept + { + return m_type == value_t::object; + } + + /*! + @brief return whether value is an array + + This function returns true iff the JSON value is an array. + + @return `true` if type is array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_array()` for all JSON + types.,is_array} + + @since version 1.0.0 + */ + constexpr bool is_array() const noexcept + { + return m_type == value_t::array; + } + + /*! + @brief return whether value is a string + + This function returns true iff the JSON value is a string. + + @return `true` if type is string, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_string()` for all JSON + types.,is_string} + + @since version 1.0.0 + */ + constexpr bool is_string() const noexcept + { + return m_type == value_t::string; + } + + /*! + @brief return whether value is discarded + + This function returns true iff the JSON value was discarded during parsing + with a callback function (see @ref parser_callback_t). + + @note This function will always be `false` for JSON values after parsing. + That is, discarded values can only occur during parsing, but will be + removed when inside a structured value or replaced by null in other cases. + + @return `true` if type is discarded, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_discarded()` for all JSON + types.,is_discarded} + + @since version 1.0.0 + */ + constexpr bool is_discarded() const noexcept + { + return m_type == value_t::discarded; + } + + /*! + @brief return the type of the JSON value (implicit) + + Implicitly return the type of the JSON value as a value from the @ref + value_t enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies the @ref value_t operator for + all JSON types.,operator__value_t} + + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept + { + return m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get an object (explicit) + template::value and + std::is_convertible::value, int>::type = 0> + T get_impl(T*) const + { + if (is_object()) + { + return T(m_value.object->begin(), m_value.object->end()); + } + else + { + throw std::domain_error("type must be object, but is " + type_name()); + } + } + + /// get an object (explicit) + object_t get_impl(object_t*) const + { + if (is_object()) + { + return *(m_value.object); + } + else + { + throw std::domain_error("type must be object, but is " + type_name()); + } + } + + /// get an array (explicit) + template::value and + not std::is_same::value and + not std::is_arithmetic::value and + not std::is_convertible::value and + not has_mapped_type::value, int>::type = 0> + T get_impl(T*) const + { + if (is_array()) + { + T to_vector; + std::transform(m_value.array->begin(), m_value.array->end(), + std::inserter(to_vector, to_vector.end()), [](basic_json i) + { + return i.get(); + }); + return to_vector; + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get an array (explicit) + template::value and + not std::is_same::value, int>::type = 0> + std::vector get_impl(std::vector*) const + { + if (is_array()) + { + std::vector to_vector; + to_vector.reserve(m_value.array->size()); + std::transform(m_value.array->begin(), m_value.array->end(), + std::inserter(to_vector, to_vector.end()), [](basic_json i) + { + return i.get(); + }); + return to_vector; + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get an array (explicit) + template::value and + not has_mapped_type::value, int>::type = 0> + T get_impl(T*) const + { + if (is_array()) + { + return T(m_value.array->begin(), m_value.array->end()); + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get an array (explicit) + array_t get_impl(array_t*) const + { + if (is_array()) + { + return *(m_value.array); + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get a string (explicit) + template::value, int>::type = 0> + T get_impl(T*) const + { + if (is_string()) + { + return *m_value.string; + } + else + { + throw std::domain_error("type must be string, but is " + type_name()); + } + } + + /// get a number (explicit) + template::value, int>::type = 0> + T get_impl(T*) const + { + switch (m_type) + { + case value_t::number_integer: + { + return static_cast(m_value.number_integer); + } + + case value_t::number_unsigned: + { + return static_cast(m_value.number_unsigned); + } + + case value_t::number_float: + { + return static_cast(m_value.number_float); + } + + default: + { + throw std::domain_error("type must be number, but is " + type_name()); + } + } + } + + /// get a boolean (explicit) + constexpr boolean_t get_impl(boolean_t*) const + { + return is_boolean() + ? m_value.boolean + : throw std::domain_error("type must be boolean, but is " + type_name()); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t*) noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t*) const noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t*) noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t*) const noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t*) noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t*) const noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t*) noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t*) const noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t*) noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t*) const noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t*) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t*) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t*) noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t*) const noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This funcion helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw std::domain_error if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // helper type + using PointerType = typename std::add_pointer::type; + + // delegate the call to get_ptr<>() + auto ptr = obj.template get_ptr(); + + if (ptr != nullptr) + { + return *ptr; + } + else + { + throw std::domain_error("incompatible ReferenceType for get_ref, actual type is " + + obj.type_name()); + } + } + + public: + + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays + + @return copy of the JSON value, converted to type @a ValueType + + @throw std::domain_error in case passed type @a ValueType is incompatible + to JSON; example: `"type must be object, but is null"` + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get__ValueType_const} + + @internal + The idea of using a casted null pointer to choose the correct + implementation is from . + @endinternal + + @sa @ref operator ValueType() const for implicit conversion + @sa @ref get() for pointer-member access + + @since version 1.0.0 + */ + template::value, int>::type = 0> + ValueType get() const + { + return get_impl(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get() noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template::value, int>::type = 0> + constexpr const PointerType get() const noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (implicit) + + Implicit pointer access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the pointee of the result yields an undefined + state. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. Enforced by a static + assertion. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get_ptr} + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get_ptr() noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (implicit) + @copydoc get_ptr() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + constexpr const PointerType get_ptr() const noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a reference value (implicit) + + Implict reference access to the internally stored JSON value. No copies + are made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. Enforced by static assertion. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + std::domain_error otherwise + + @throw std::domain_error in case passed type @a ReferenceType is + incompatible with the stored JSON value + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.1.0 + */ + template::value, int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw std::domain_error in case passed type @a ValueType is incompatible + to JSON, thrown by @ref get() const + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename std::enable_if < + not std::is_pointer::value and + not std::is_same::value +#ifndef _MSC_VER // Fix for issue #167 operator<< abiguity under VS2015 + and not std::is_same>::value +#endif + , int >::type = 0 > + operator ValueType() const + { + // delegate the call to get<>() const + return get(); + } + + /// @} + + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()`; example: `"array index 7 is out of range"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read and + written using `at()`.,at__size_type} + + @since version 1.0.0 + */ + reference at(size_type idx) + { + // at only works for arrays + if (is_array()) + { + try + { + return m_value.array->at(idx); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + } + else + { + throw std::domain_error("cannot use at() with " + type_name()); + } + } + + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, + with bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()`; example: `"array index 7 is out of range"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + `at()`.,at__size_type_const} + + @since version 1.0.0 + */ + const_reference at(size_type idx) const + { + // at only works for arrays + if (is_array()) + { + try + { + return m_value.array->at(idx); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + } + else + { + throw std::domain_error("cannot use at() with " + type_name()); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()`; example: `"key "the fast" not found"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using `at()`.,at__object_t_key_type} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (is_object()) + { + try + { + return m_value.object->at(key); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("key '" + key + "' not found"); + } + } + else + { + throw std::domain_error("cannot use at() with " + type_name()); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, + with bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()`; example: `"key "the fast" not found"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + `at()`.,at__object_t_key_type_const} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (is_object()) + { + try + { + return m_value.object->at(key); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("key '" + key + "' not found"); + } + } + else + { + throw std::domain_error("cannot use at() with " + type_name()); + } + } + + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array or null; example: + `"cannot use operator[] with string"` + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using `[]` operator. Note the addition of `null` + values.,operatorarray__size_type} + + @since version 1.0.0 + */ + reference operator[](size_type idx) + { + // implicitly convert null value to an empty array + if (is_null()) + { + m_type = value_t::array; + m_value.array = create(); + assert_invariant(); + } + + // operator[] only works for arrays + if (is_array()) + { + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) + { + m_value.array->insert(m_value.array->end(), + idx - m_value.array->size() + 1, + basic_json()); + } + + return m_value.array->operator[](idx); + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array; example: `"cannot use + operator[] with null"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the `[]` operator.,operatorarray__size_type_const} + + @since version 1.0.0 + */ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (is_array()) + { + return m_value.array->operator[](idx); + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference operator[](const typename object_t::key_type& key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (is_object()) + { + return m_value.object->operator[](key); + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (is_object()) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + template + reference operator[](T * (&key)[n]) + { + return operator[](static_cast(key)); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @note This function is required for compatibility reasons with Clang. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + template + const_reference operator[](T * (&key)[n]) const + { + return operator[](static_cast(key)); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + reference operator[](T* key) + { + // implicitly convert null to object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // at only works for objects + if (is_object()) + { + return m_value.object->operator[](key); + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + const_reference operator[](T* key) const + { + // at only works for objects + if (is_object()) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + else + { + throw std::domain_error("cannot use operator[] with " + type_name()); + } + } + + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(std::out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw std::domain_error if JSON is not an object; example: `"cannot use + value() with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + template::value, int>::type = 0> + ValueType value(const typename object_t::key_type& key, ValueType default_value) const + { + // at only works for objects + if (is_object()) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + else + { + return default_value; + } + } + else + { + throw std::domain_error("cannot use value() with " + type_name()); + } + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /*! + @brief access specified object element via JSON Pointer with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(ptr); + } catch(std::out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const json_pointer&), this function does not throw + if the given key @a key was not found. + + @param[in] ptr a JSON pointer to the element to access + @param[in] default_value the value to return if @a ptr found no value + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw std::domain_error if JSON is not an object; example: `"cannot use + value() with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value_ptr} + + @sa @ref operator[](const json_pointer&) for unchecked access by reference + + @since version 2.0.2 + */ + template::value, int>::type = 0> + ValueType value(const json_pointer& ptr, ValueType default_value) const + { + // at only works for objects + if (is_object()) + { + // if pointer resolves a value, return it or use default value + try + { + return ptr.get_checked(this); + } + catch (std::out_of_range&) + { + return default_value; + } + } + else + { + throw std::domain_error("cannot use value() with " + type_name()); + } + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const json_pointer&, ValueType) const + */ + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /*! + @brief access the first element + + Returns a reference to the first element in the container. For a JSON + container `c`, the expression `c.front()` is equivalent to `*c.begin()`. + + @return In case of a structured type (array or object), a reference to the + first element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw std::out_of_range when called on `null` value + + @liveexample{The following code shows an example for `front()`.,front} + + @sa @ref back() -- access the last element + + @since version 1.0.0 + */ + reference front() + { + return *begin(); + } + + /*! + @copydoc basic_json::front() + */ + const_reference front() const + { + return *cbegin(); + } + + /*! + @brief access the last element + + Returns a reference to the last element in the container. For a JSON + container `c`, the expression `c.back()` is equivalent to + @code {.cpp} + auto tmp = c.end(); + --tmp; + return *tmp; + @endcode + + @return In case of a structured type (array or object), a reference to the + last element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw std::out_of_range when called on `null` value. + + @liveexample{The following code shows an example for `back()`.,back} + + @sa @ref front() -- access the first element + + @since version 1.0.0 + */ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /*! + @copydoc basic_json::back() + */ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /*! + @brief remove element given an iterator + + Removes the element specified by iterator @a pos. The iterator @a pos must + be valid and dereferenceable. Thus the `end()` iterator (which is valid, + but is not dereferenceable) cannot be used as a value for @a pos. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] pos iterator to the element to remove + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw std::domain_error if called on a `null` value; example: `"cannot + use erase() with null"` + @throw std::domain_error if called on an iterator which does not belong to + the current JSON value; example: `"iterator does not fit current value"` + @throw std::out_of_range if called on a primitive type with invalid + iterator (i.e., any iterator which is not `begin()`); example: `"iterator + out of range"` + + @complexity The complexity depends on the type: + - objects: amortized constant + - arrays: linear in distance between pos and the end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType} + + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType pos) + { + // make sure iterator fits the current value + if (this != pos.m_object) + { + throw std::domain_error("iterator does not fit current value"); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (not pos.m_it.primitive_iterator.is_begin()) + { + throw std::out_of_range("iterator out of range"); + } + + if (is_string()) + { + AllocatorType alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + { + throw std::domain_error("cannot use erase() with " + type_name()); + } + } + + return result; + } + + /*! + @brief remove elements given an iterator range + + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] first iterator to the beginning of the range to remove + @param[in] last iterator past the end of the range to remove + @return Iterator following the last removed element. If the iterator @a + second refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw std::domain_error if called on a `null` value; example: `"cannot + use erase() with null"` + @throw std::domain_error if called on iterators which does not belong to + the current JSON value; example: `"iterators do not fit current value"` + @throw std::out_of_range if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` + + @complexity The complexity depends on the type: + - objects: `log(size()) + std::distance(first, last)` + - arrays: linear in the distance between @a first and @a last, plus linear + in the distance between @a last and end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType_IteratorType} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType first, IteratorType last) + { + // make sure iterator fits the current value + if (this != first.m_object or this != last.m_object) + { + throw std::domain_error("iterators do not fit current value"); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) + { + throw std::out_of_range("iterators out of range"); + } + + if (is_string()) + { + AllocatorType alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + { + throw std::domain_error("cannot use erase() with " + type_name()); + } + } + + return result; + } + + /*! + @brief remove element from a JSON object given a key + + Removes elements from a JSON object with the key value @a key. + + @param[in] key value of the elements to remove + + @return Number of elements removed. If @a ObjectType is the default + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). + + @post References and iterators to the erased elements are invalidated. + Other references and iterators are not affected. + + @throw std::domain_error when called on a type other than JSON object; + example: `"cannot use erase() with null"` + + @complexity `log(size()) + count(key)` + + @liveexample{The example shows the effect of `erase()`.,erase__key_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + size_type erase(const typename object_t::key_type& key) + { + // this erase only works for objects + if (is_object()) + { + return m_value.object->erase(key); + } + else + { + throw std::domain_error("cannot use erase() with " + type_name()); + } + } + + /*! + @brief remove element from a JSON array given an index + + Removes element from a JSON array at the index @a idx. + + @param[in] idx index of the element to remove + + @throw std::domain_error when called on a type other than JSON array; + example: `"cannot use erase() with null"` + @throw std::out_of_range when `idx >= size()`; example: `"array index 17 + is out of range"` + + @complexity Linear in distance between @a idx and the end of the container. + + @liveexample{The example shows the effect of `erase()`.,erase__size_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 + */ + void erase(const size_type idx) + { + // this erase only works for arrays + if (is_array()) + { + if (idx >= size()) + { + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else + { + throw std::domain_error("cannot use erase() with " + type_name()); + } + } + + /// @} + + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /*! + @brief find an element in a JSON object + + Finds an element in a JSON object with key equivalent to @a key. If the + element is not found or the JSON value is not an object, end() is + returned. + + @note This method always returns @ref end() when executed on a JSON type + that is not an object. + + @param[in] key key value of the element to search for + + @return Iterator to an element with key equivalent to @a key. If no such + element is found or the JSON value is not an object, past-the-end (see + @ref end()) iterator is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `find()` is used.,find__key_type} + + @since version 1.0.0 + */ + iterator find(typename object_t::key_type key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(key); + } + + return result; + } + + /*! + @brief find an element in a JSON object + @copydoc find(typename object_t::key_type) + */ + const_iterator find(typename object_t::key_type key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(key); + } + + return result; + } + + /*! + @brief returns the number of occurrences of a key in a JSON object + + Returns the number of elements with key @a key. If ObjectType is the + default `std::map` type, the return value will always be `0` (@a key was + not found) or `1` (@a key was found). + + @note This method always returns `0` when executed on a JSON type that is + not an object. + + @param[in] key key value of the element to count + + @return Number of elements with key @a key. If the JSON value is not an + object, the return value will be `0`. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `count()` is used.,count} + + @since version 1.0.0 + */ + size_type count(typename object_t::key_type key) const + { + // return 0 for all nonobject types + return is_object() ? m_value.object->count(key) : 0; + } + + /// @} + + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /*! + @brief returns an iterator to the first element + + Returns an iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `begin()`.,begin} + + @sa @ref cbegin() -- returns a const iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /*! + @copydoc basic_json::cbegin() + */ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /*! + @brief returns a const iterator to the first element + + Returns a const iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).begin()`. + + @liveexample{The following code shows an example for `cbegin()`.,cbegin} + + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /*! + @brief returns an iterator to one past the last element + + Returns an iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `end()`.,end} + + @sa @ref cend() -- returns a const iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /*! + @copydoc basic_json::cend() + */ + const_iterator end() const noexcept + { + return cend(); + } + + /*! + @brief returns a const iterator to one past the last element + + Returns a const iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).end()`. + + @liveexample{The following code shows an example for `cend()`.,cend} + + @sa @ref end() -- returns an iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /*! + @brief returns an iterator to the reverse-beginning + + Returns an iterator to the reverse-beginning; that is, the last element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(end())`. + + @liveexample{The following code shows an example for `rbegin()`.,rbegin} + + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /*! + @copydoc basic_json::crbegin() + */ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /*! + @brief returns an iterator to the reverse-end + + Returns an iterator to the reverse-end; that is, one before the first + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(begin())`. + + @liveexample{The following code shows an example for `rend()`.,rend} + + @sa @ref crend() -- returns a const reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /*! + @copydoc basic_json::crend() + */ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /*! + @brief returns a const reverse iterator to the last element + + Returns a const iterator to the reverse-beginning; that is, the last + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rbegin()`. + + @liveexample{The following code shows an example for `crbegin()`.,crbegin} + + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /*! + @brief returns a const reverse iterator to one before the first + + Returns a const reverse iterator to the reverse-end; that is, one before + the first element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rend()`. + + @liveexample{The following code shows an example for `crend()`.,crend} + + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + private: + // forward declaration + template class iteration_proxy; + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + @note The name of this function is not yet final and may change in the + future. + */ + static iteration_proxy iterator_wrapper(reference cont) + { + return iteration_proxy(cont); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + static iteration_proxy iterator_wrapper(const_reference cont) + { + return iteration_proxy(cont); + } + + /// @} + + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /*! + @brief checks whether the container is empty + + Checks if a JSON value has no elements. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `true` + boolean | `false` + string | `false` + number | `false` + object | result of function `object_t::empty()` + array | result of function `array_t::empty()` + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant + complexity. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `begin() == end()`. + + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + bool empty() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_value.object->empty(); + } + + default: + { + // all other types are nonempty + return false; + } + } + } + + /*! + @brief returns the number of elements + + Returns the number of elements in a JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` + boolean | `1` + string | `1` + number | `1` + object | result of function object_t::size() + array | result of function array_t::size() + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `std::distance(begin(), end())`. + + @liveexample{The following code calls `size()` on the different value + types.,size} + + @sa @ref empty() -- checks whether the container is empty + @sa @ref max_size() -- returns the maximal number of elements + + @since version 1.0.0 + */ + size_type size() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_value.object->size(); + } + + default: + { + // all other types have size 1 + return 1; + } + } + } + + /*! + @brief returns the maximum possible number of elements + + Returns the maximum number of elements a JSON value is able to hold due to + system or library implementation limitations, i.e. `std::distance(begin(), + end())` for the JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` (same as `size()`) + boolean | `1` (same as `size()`) + string | `1` (same as `size()`) + number | `1` (same as `size()`) + object | result of function `object_t::max_size()` + array | result of function `array_t::max_size()` + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant + complexity. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of returning `b.size()` where `b` is the largest + possible JSON value. + + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + size_type max_size() const noexcept + { + switch (m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_value.object->max_size(); + } + + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /*! + @brief clears the contents + + Clears the content of a JSON value and resets it to the default value as + if @ref basic_json(value_t) would have been called: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows the effect of `clear()` to different + JSON types.,clear} + + @since version 1.0.0 + */ + void clear() noexcept + { + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: + { + m_value.number_float = 0.0; + break; + } + + case value_t::boolean: + { + m_value.boolean = false; + break; + } + + case value_t::string: + { + m_value.string->clear(); + break; + } + + case value_t::array: + { + m_value.array->clear(); + break; + } + + case value_t::object: + { + m_value.object->clear(); + break; + } + + default: + { + break; + } + } + } + + /*! + @brief add an object to an array + + Appends the given element @a val to the end of the JSON value. If the + function is called on a JSON null value, an empty array is created before + appending @a val. + + @param[in] val the value to add to the JSON array + + @throw std::domain_error when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON array. Note how the `null` value was silently + converted to a JSON array.,push_back} + + @since version 1.0.0 + */ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (not(is_null() or is_array())) + { + throw std::domain_error("cannot use push_back() with " + type_name()); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (move semantics) + m_value.array->push_back(std::move(val)); + // invalidate object + val.m_type = value_t::null; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (not(is_null() or is_array())) + { + throw std::domain_error("cannot use push_back() with " + type_name()); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array + m_value.array->push_back(val); + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + Inserts the given element @a val to the JSON object. If the function is + called on a JSON null value, an empty object is created before inserting + @a val. + + @param[in] val the value to add to the JSON object + + @throw std::domain_error when called on a type other than JSON object or + null; example: `"cannot use push_back() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON object. Note how the `null` value was silently + converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 + */ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (not(is_null() or is_object())) + { + throw std::domain_error("cannot use push_back() with " + type_name()); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array + m_value.object->insert(val); + } + + /*! + @brief add an object to an object + @copydoc push_back(const typename object_t::value_type&) + */ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(std::initializer_list init) + { + if (is_object() and init.size() == 2 and init.begin()->is_string()) + { + const string_t key = *init.begin(); + push_back(typename object_t::value_type(key, *(init.begin() + 1))); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(std::initializer_list) + */ + reference operator+=(std::initializer_list init) + { + push_back(init); + return *this; + } + + /*! + @brief add an object to an array + + Creates a JSON value from the passed parameters @a args to the end of the + JSON value. If the function is called on a JSON null value, an empty array + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @throw std::domain_error when called on a type other than JSON array or + null; example: `"cannot use emplace_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` can be used to add + elements to a JSON array. Note how the `null` value was silently converted + to a JSON array.,emplace_back} + + @since version 2.0.8 + */ + template + void emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (not(is_null() or is_array())) + { + throw std::domain_error("cannot use emplace_back() with " + type_name()); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) + m_value.array->emplace_back(std::forward(args)...); + } + + /*! + @brief add an object to an object if key does not exist + + Inserts a new element into a JSON object constructed in-place with the given + @a args if there is no element with the key in the container. If the + function is called on a JSON null value, an empty object is created before + appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return a pair consisting of an iterator to the inserted element, or the + already-existing element if no insertion happened, and a bool + denoting whether the insertion took place. + + @throw std::domain_error when called on a type other than JSON object or + null; example: `"cannot use emplace() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `emplace()` can be used to add elements + to a JSON object. Note how the `null` value was silently converted to a + JSON object. Further note how no value is added if there was already one + value stored with the same key.,emplace} + + @since version 2.0.8 + */ + template + std::pair emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (not(is_null() or is_object())) + { + throw std::domain_error("cannot use emplace() with " + type_name()); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + auto res = m_value.object->emplace(std::forward(args)...); + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; + } + + /*! + @brief inserts element + + Inserts element @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] val element to insert + @return iterator pointing to the inserted @a val. + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + + @complexity Constant plus linear in the distance between pos and end of the + container. + + @liveexample{The example shows how `insert()` is used.,insert} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const basic_json& val) + { + // insert only works for arrays + if (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } + else + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + } + + /*! + @brief inserts element + @copydoc insert(const_iterator, const basic_json&) + */ + iterator insert(const_iterator pos, basic_json&& val) + { + return insert(pos, val); + } + + /*! + @brief inserts elements + + Inserts @a cnt copies of @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert + @return iterator pointing to the first element inserted, or @a pos if + `cnt==0` + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + + @complexity Linear in @a cnt plus linear in the distance between @a pos + and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__count} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + // insert only works for arrays + if (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } + else + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)` before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + @throw std::domain_error if @a first and @a last do not belong to the same + JSON value; example: `"iterators do not fit"` + @throw std::domain_error if @a first or @a last are iterators into + container for which insert is called; example: `"passed iterators may not + belong to container"` + + @return iterator pointing to the first element inserted, or @a pos if + `first==last` + + @complexity Linear in `std::distance(first, last)` plus linear in the + distance between @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__range} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + // insert only works for arrays + if (not is_array()) + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // check if range iterators belong to the same JSON object + if (first.m_object != last.m_object) + { + throw std::domain_error("iterators do not fit"); + } + + if (first.m_object == this or last.m_object == this) + { + throw std::domain_error("passed iterators may not belong to container"); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert( + pos.m_it.array_iterator, + first.m_it.array_iterator, + last.m_it.array_iterator); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from initializer list @a ilist before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] ilist initializer list to insert the values from + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + + @return iterator pointing to the first element inserted, or @a pos if + `ilist` is empty + + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__ilist} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, std::initializer_list ilist) + { + // insert only works for arrays + if (not is_array()) + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist); + return result; + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); + assert_invariant(); + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw std::domain_error when JSON value is not an array; example: `"cannot + use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how arrays can be swapped with + `swap()`.,swap__array_t} + + @since version 1.0.0 + */ + void swap(array_t& other) + { + // swap only works for arrays + if (is_array()) + { + std::swap(*(m_value.array), other); + } + else + { + throw std::domain_error("cannot use swap() with " + type_name()); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON object with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other object to exchange the contents with + + @throw std::domain_error when JSON value is not an object; example: + `"cannot use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how objects can be swapped with + `swap()`.,swap__object_t} + + @since version 1.0.0 + */ + void swap(object_t& other) + { + // swap only works for objects + if (is_object()) + { + std::swap(*(m_value.object), other); + } + else + { + throw std::domain_error("cannot use swap() with " + type_name()); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other string to exchange the contents with + + @throw std::domain_error when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__string_t} + + @since version 1.0.0 + */ + void swap(string_t& other) + { + // swap only works for strings + if (is_string()) + { + std::swap(*(m_value.string), other); + } + else + { + throw std::domain_error("cannot use swap() with " + type_name()); + } + } + + /// @} + + + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + private: + /*! + @brief comparison operator for JSON types + + Returns an ordering that is similar to Python: + - order: null < boolean < number < object < array < string + - furthermore, each type is not smaller than itself + + @since version 1.0.0 + */ + friend bool operator<(const value_t lhs, const value_t rhs) noexcept + { + static constexpr std::array order = {{ + 0, // null + 3, // object + 4, // array + 5, // string + 1, // boolean + 2, // integer + 2, // unsigned + 2, // float + } + }; + + // discarded values are not comparable + if (lhs == value_t::discarded or rhs == value_t::discarded) + { + return false; + } + + return order[static_cast(lhs)] < order[static_cast(rhs)]; + } + + public: + /*! + @brief comparison: equal + + Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) they are from the same type and (2) + their stored values are the same. + - Integer and floating-point numbers are automatically converted before + comparison. Floating-point numbers are compared indirectly: two + floating-point numbers `f1` and `f2` are considered equal if neither + `f1 > f2` nor `f2 > f1` holds. + - Two JSON null values are equal. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are equal + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__equal} + + @since version 1.0.0 + */ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + { + return *lhs.m_value.array == *rhs.m_value.array; + } + case value_t::object: + { + return *lhs.m_value.object == *rhs.m_value.object; + } + case value_t::null: + { + return true; + } + case value_t::string: + { + return *lhs.m_value.string == *rhs.m_value.string; + } + case value_t::boolean: + { + return lhs.m_value.boolean == rhs.m_value.boolean; + } + case value_t::number_integer: + { + return lhs.m_value.number_integer == rhs.m_value.number_integer; + } + case value_t::number_unsigned: + { + return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; + } + case value_t::number_float: + { + return lhs.m_value.number_float == rhs.m_value.number_float; + } + default: + { + return false; + } + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); + } + + return false; + } + + /*! + @brief comparison: equal + + The functions compares the given JSON value against a null pointer. As the + null pointer can be used to initialize a JSON value to null, a comparison + of JSON value @a v with a null pointer should be equivalent to call + `v.is_null()`. + + @param[in] v JSON value to consider + @return whether @a v is null + + @complexity Constant. + + @liveexample{The example compares several JSON types to the null pointer. + ,operator__equal__nullptr_t} + + @since version 1.0.0 + */ + friend bool operator==(const_reference v, std::nullptr_t) noexcept + { + return v.is_null(); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, std::nullptr_t) + */ + friend bool operator==(std::nullptr_t, const_reference v) noexcept + { + return v.is_null(); + } + + /*! + @brief comparison: not equal + + Compares two JSON values for inequality by calculating `not (lhs == rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are not equal + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__notequal} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs == rhs); + } + + /*! + @brief comparison: not equal + + The functions compares the given JSON value against a null pointer. As the + null pointer can be used to initialize a JSON value to null, a comparison + of JSON value @a v with a null pointer should be equivalent to call + `not v.is_null()`. + + @param[in] v JSON value to consider + @return whether @a v is not null + + @complexity Constant. + + @liveexample{The example compares several JSON types to the null pointer. + ,operator__notequal__nullptr_t} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference v, std::nullptr_t) noexcept + { + return not v.is_null(); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, std::nullptr_t) + */ + friend bool operator!=(std::nullptr_t, const_reference v) noexcept + { + return not v.is_null(); + } + + /*! + @brief comparison: less than + + Compares whether one JSON value @a lhs is less than another JSON value @a + rhs according to the following rules: + - If @a lhs and @a rhs have the same type, the values are compared using + the default `<` operator. + - Integer and floating-point numbers are automatically converted before + comparison + - In case @a lhs and @a rhs have different types, the values are ignored + and the order of the types is considered, see + @ref operator<(const value_t, const value_t). + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__less} + + @since version 1.0.0 + */ + friend bool operator<(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + { + return *lhs.m_value.array < *rhs.m_value.array; + } + case value_t::object: + { + return *lhs.m_value.object < *rhs.m_value.object; + } + case value_t::null: + { + return false; + } + case value_t::string: + { + return *lhs.m_value.string < *rhs.m_value.string; + } + case value_t::boolean: + { + return lhs.m_value.boolean < rhs.m_value.boolean; + } + case value_t::number_integer: + { + return lhs.m_value.number_integer < rhs.m_value.number_integer; + } + case value_t::number_unsigned: + { + return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; + } + case value_t::number_float: + { + return lhs.m_value.number_float < rhs.m_value.number_float; + } + default: + { + return false; + } + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); + } + + /*! + @brief comparison: less than or equal + + Compares whether one JSON value @a lhs is less than or equal to another + JSON value by calculating `not (rhs < lhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than or equal to @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greater} + + @since version 1.0.0 + */ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + return not (rhs < lhs); + } + + /*! + @brief comparison: greater than + + Compares whether one JSON value @a lhs is greater than another + JSON value by calculating `not (lhs <= rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than to @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__lessequal} + + @since version 1.0.0 + */ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs <= rhs); + } + + /*! + @brief comparison: greater than or equal + + Compares whether one JSON value @a lhs is greater than or equal to another + JSON value by calculating `not (lhs < rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than or equal to @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greaterequal} + + @since version 1.0.0 + */ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs < rhs); + } + + /// @} + + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ + + /*! + @brief serialize to stream + + Serialize the given JSON value @a j to the output stream @a o. The JSON + value will be serialized using the @ref dump member function. The + indentation of the output can be controlled with the member variable + `width` of the output stream @a o. For instance, using the manipulator + `std::setw(4)` on @a o sets the indentation level to `4` and the + serialization result is the same as calling `dump(4)`. + + @note During serializaion, the locale and the precision of the output + stream @a o are changed. The original values are restored when the + function returns. + + @param[in,out] o stream to serialize to + @param[in] j JSON value to serialize + + @return the stream @a o + + @complexity Linear. + + @liveexample{The example below shows the serialization with different + parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0 + */ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as indentation parameter if nonzero + const bool pretty_print = (o.width() > 0); + const auto indentation = (pretty_print ? o.width() : 0); + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // fix locale problems + const auto old_locale = o.imbue(std::locale::classic()); + // set precision + + // 6, 15 or 16 digits of precision allows round-trip IEEE 754 + // string->float->string, string->double->string or string->long + // double->string; to be safe, we read this value from + // std::numeric_limits::digits10 + const auto old_precision = o.precision(std::numeric_limits::digits10); + + // do the actual serialization + j.dump(o, pretty_print, static_cast(indentation)); + + // reset locale and precision + o.imbue(old_locale); + o.precision(old_precision); + return o; + } + + /*! + @brief serialize to stream + @copydoc operator<<(std::ostream&, const basic_json&) + */ + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } + + /// @} + + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /*! + @brief deserialize from an array + + This function reads from an array of 1-byte values. + + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @param[in] array array to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an array.,parse__array__parser_callback_t} + + @since version 2.0.3 + */ + template + static basic_json parse(T (&array)[N], + const parser_callback_t cb = nullptr) + { + // delegate the call to the iterator-range parse overload + return parse(std::begin(array), std::end(array), cb); + } + + /*! + @brief deserialize from string literal + + @tparam CharT character/literal type with size of 1 byte + @param[in] s string literal to read a serialized JSON value from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + @note String containers like `std::string` or @ref string_t can be parsed + with @ref parse(const ContiguousContainer&, const parser_callback_t) + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} + + @sa @ref parse(std::istream&, const parser_callback_t) for a version that + reads from an input stream + + @since version 1.0.0 (originally for @ref string_t) + */ + template::value and + std::is_integral::type>::value and + sizeof(typename std::remove_pointer::type) == 1, int>::type = 0> + static basic_json parse(const CharT s, + const parser_callback_t cb = nullptr) + { + return parser(reinterpret_cast(s), cb).parse(); + } + + /*! + @brief deserialize from stream + + @param[in,out] i stream to read a serialized JSON value from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} + + @sa @ref parse(const CharT, const parser_callback_t) for a version + that reads from a string + + @since version 1.0.0 + */ + static basic_json parse(std::istream& i, + const parser_callback_t cb = nullptr) + { + return parser(i, cb).parse(); + } + + /*! + @copydoc parse(std::istream&, const parser_callback_t) + */ + static basic_json parse(std::istream&& i, + const parser_callback_t cb = nullptr) + { + return parser(i, cb).parse(); + } + + /*! + @brief deserialize from an iterator range with contiguous storage + + This function reads from an iterator range of a container with contiguous + storage of 1-byte values. Compatible container types include + `std::vector`, `std::string`, `std::array`, `std::valarray`, and + `std::initializer_list`. Furthermore, C-style arrays can be used with + `std::begin()`/`std::end()`. User-defined containers can be used as long + as they implement random-access iterators and a contiguous storage. + + @pre The iterator range is contiguous. Violating this precondition yields + undefined behavior. **This precondition is enforced with an assertion.** + @pre Each element in the range has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with noncompliant iterators and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @tparam IteratorType iterator of container with contiguous storage + @param[in] first begin of the range to parse (included) + @param[in] last end of the range to parse (excluded) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an iterator range.,parse__iteratortype__parser_callback_t} + + @since version 2.0.3 + */ + template::iterator_category>::value, int>::type = 0> + static basic_json parse(IteratorType first, IteratorType last, + const parser_callback_t cb = nullptr) + { + // assertion to check that the iterator range is indeed contiguous, + // see http://stackoverflow.com/a/35008842/266378 for more discussion + assert(std::accumulate(first, last, std::make_pair(true, 0), + [&first](std::pair res, decltype(*first) val) + { + res.first &= (val == *(std::next(std::addressof(*first), res.second++))); + return res; + }).first); + + // assertion to check that each element is 1 byte long + static_assert(sizeof(typename std::iterator_traits::value_type) == 1, + "each element in the iterator range must have the size of 1 byte"); + + // if iterator range is empty, create a parser with an empty string + // to generate "unexpected EOF" error message + if (std::distance(first, last) <= 0) + { + return parser("").parse(); + } + + return parser(first, last, cb).parse(); + } + + /*! + @brief deserialize from a container with contiguous storage + + This function reads from a container with contiguous storage of 1-byte + values. Compatible container types include `std::vector`, `std::string`, + `std::array`, and `std::initializer_list`. User-defined containers can be + used as long as they implement random-access iterators and a contiguous + storage. + + @pre The container storage is contiguous. Violating this precondition + yields undefined behavior. **This precondition is enforced with an + assertion.** + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with a noncompliant container and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @tparam ContiguousContainer container type with contiguous storage + @param[in] c container to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from a contiguous container.,parse__contiguouscontainer__parser_callback_t} + + @since version 2.0.3 + */ + template::value and + std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits()))>::iterator_category>::value + , int>::type = 0> + static basic_json parse(const ContiguousContainer& c, + const parser_callback_t cb = nullptr) + { + // delegate the call to the iterator-range parse overload + return parse(std::begin(c), std::end(c), cb); + } + + /*! + @brief deserialize from stream + + Deserializes an input stream to a JSON value. + + @param[in,out] i input stream to read a serialized JSON value from + @param[in,out] j JSON value to write the deserialized input to + + @throw std::invalid_argument in case of parse errors + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below shows how a JSON value is constructed by + reading a serialization from a stream.,operator_deserialize} + + @sa parse(std::istream&, const parser_callback_t) for a variant with a + parser callback function to filter values while parsing + + @since version 1.0.0 + */ + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + j = parser(i).parse(); + return i; + } + + /*! + @brief deserialize from stream + @copydoc operator<<(basic_json&, std::istream&) + */ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + j = parser(i).parse(); + return i; + } + + /// @} + + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + private: + template + static void add_to_vector(std::vector& vec, size_t bytes, const T number) + { + assert(bytes == 1 or bytes == 2 or bytes == 4 or bytes == 8); + + switch (bytes) + { + case 8: + { + vec.push_back(static_cast((number >> 070) & 0xff)); + vec.push_back(static_cast((number >> 060) & 0xff)); + vec.push_back(static_cast((number >> 050) & 0xff)); + vec.push_back(static_cast((number >> 040) & 0xff)); + // intentional fall-through + } + + case 4: + { + vec.push_back(static_cast((number >> 030) & 0xff)); + vec.push_back(static_cast((number >> 020) & 0xff)); + // intentional fall-through + } + + case 2: + { + vec.push_back(static_cast((number >> 010) & 0xff)); + // intentional fall-through + } + + case 1: + { + vec.push_back(static_cast(number & 0xff)); + break; + } + } + } + + /*! + @brief take sufficient bytes from a vector to fill an integer variable + + In the context of binary serialization formats, we need to read several + bytes from a byte vector and combine them to multi-byte integral data + types. + + @param[in] vec byte vector to read from + @param[in] current_index the position in the vector after which to read + + @return the next sizeof(T) bytes from @a vec, in reverse order as T + + @tparam T the integral return type + + @throw std::out_of_range if there are less than sizeof(T)+1 bytes in the + vector @a vec to read + + In the for loop, the bytes from the vector are copied in reverse order into + the return value. In the figures below, let sizeof(T)=4 and `i` be the loop + variable. + + Precondition: + + vec: | | | a | b | c | d | T: | | | | | + ^ ^ ^ ^ + current_index i ptr sizeof(T) + + Postcondition: + + vec: | | | a | b | c | d | T: | d | c | b | a | + ^ ^ ^ + | i ptr + current_index + + @sa Code adapted from . + */ + template + static T get_from_vector(const std::vector& vec, const size_t current_index) + { + if (current_index + sizeof(T) + 1 > vec.size()) + { + throw std::out_of_range("cannot read " + std::to_string(sizeof(T)) + " bytes from vector"); + } + + T result; + uint8_t* ptr = reinterpret_cast(&result); + for (size_t i = 0; i < sizeof(T); ++i) + { + *ptr++ = vec[current_index + sizeof(T) - i]; + } + return result; + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + This is a straightforward implementation of the MessagePack specification. + + @param[in] j JSON value to serialize + @param[in,out] v byte vector to write the serialization to + + @sa https://github.com/msgpack/msgpack/blob/master/spec.md + */ + static void to_msgpack_internal(const basic_json& j, std::vector& v) + { + switch (j.type()) + { + case value_t::null: + { + // nil + v.push_back(0xc0); + break; + } + + case value_t::boolean: + { + // true and false + v.push_back(j.m_value.boolean ? 0xc3 : 0xc2); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT8_MAX) + { + // uint 8 + v.push_back(0xcc); + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT16_MAX) + { + // uint 16 + v.push_back(0xcd); + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT32_MAX) + { + // uint 32 + v.push_back(0xce); + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT64_MAX) + { + // uint 64 + v.push_back(0xcf); + add_to_vector(v, 8, j.m_value.number_unsigned); + } + } + else + { + if (j.m_value.number_integer >= -32) + { + // negative fixnum + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT8_MIN and j.m_value.number_integer <= INT8_MAX) + { + // int 8 + v.push_back(0xd0); + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT16_MIN and j.m_value.number_integer <= INT16_MAX) + { + // int 16 + v.push_back(0xd1); + add_to_vector(v, 2, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT32_MIN and j.m_value.number_integer <= INT32_MAX) + { + // int 32 + v.push_back(0xd2); + add_to_vector(v, 4, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT64_MIN and j.m_value.number_integer <= INT64_MAX) + { + // int 64 + v.push_back(0xd3); + add_to_vector(v, 8, j.m_value.number_integer); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT8_MAX) + { + // uint 8 + v.push_back(0xcc); + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT16_MAX) + { + // uint 16 + v.push_back(0xcd); + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT32_MAX) + { + // uint 32 + v.push_back(0xce); + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT64_MAX) + { + // uint 64 + v.push_back(0xcf); + add_to_vector(v, 8, j.m_value.number_unsigned); + } + break; + } + + case value_t::number_float: + { + // float 64 + v.push_back(0xcb); + const uint8_t* helper = reinterpret_cast(&(j.m_value.number_float)); + for (size_t i = 0; i < 8; ++i) + { + v.push_back(helper[7 - i]); + } + break; + } + + case value_t::string: + { + const auto N = j.m_value.string->size(); + if (N <= 31) + { + // fixstr + v.push_back(static_cast(0xa0 | N)); + } + else if (N <= 255) + { + // str 8 + v.push_back(0xd9); + add_to_vector(v, 1, N); + } + else if (N <= 65535) + { + // str 16 + v.push_back(0xda); + add_to_vector(v, 2, N); + } + else if (N <= 4294967295) + { + // str 32 + v.push_back(0xdb); + add_to_vector(v, 4, N); + } + + // append string + std::copy(j.m_value.string->begin(), j.m_value.string->end(), + std::back_inserter(v)); + break; + } + + case value_t::array: + { + const auto N = j.m_value.array->size(); + if (N <= 15) + { + // fixarray + v.push_back(static_cast(0x90 | N)); + } + else if (N <= 0xffff) + { + // array 16 + v.push_back(0xdc); + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + // array 32 + v.push_back(0xdd); + add_to_vector(v, 4, N); + } + + // append each element + for (const auto& el : *j.m_value.array) + { + to_msgpack_internal(el, v); + } + break; + } + + case value_t::object: + { + const auto N = j.m_value.object->size(); + if (N <= 15) + { + // fixmap + v.push_back(static_cast(0x80 | (N & 0xf))); + } + else if (N <= 65535) + { + // map 16 + v.push_back(0xde); + add_to_vector(v, 2, N); + } + else if (N <= 4294967295) + { + // map 32 + v.push_back(0xdf); + add_to_vector(v, 4, N); + } + + // append each element + for (const auto& el : *j.m_value.object) + { + to_msgpack_internal(el.first, v); + to_msgpack_internal(el.second, v); + } + break; + } + + default: + { + break; + } + } + } + + /*! + @brief create a CBOR serialization of a given JSON value + + This is a straightforward implementation of the CBOR specification. + + @param[in] j JSON value to serialize + @param[in,out] v byte vector to write the serialization to + + @sa https://tools.ietf.org/html/rfc7049 + */ + static void to_cbor_internal(const basic_json& j, std::vector& v) + { + switch (j.type()) + { + case value_t::null: + { + v.push_back(0xf6); + break; + } + + case value_t::boolean: + { + v.push_back(j.m_value.boolean ? 0xf5 : 0xf4); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_value.number_integer <= 0x17) + { + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT8_MAX) + { + v.push_back(0x18); + // one-byte uint8_t + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT16_MAX) + { + v.push_back(0x19); + // two-byte uint16_t + add_to_vector(v, 2, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT32_MAX) + { + v.push_back(0x1a); + // four-byte uint32_t + add_to_vector(v, 4, j.m_value.number_integer); + } + else + { + v.push_back(0x1b); + // eight-byte uint64_t + add_to_vector(v, 8, j.m_value.number_integer); + } + } + else + { + // The conversions below encode the sign in the first byte, + // and the value is converted to a positive number. + const auto positive_number = -1 - j.m_value.number_integer; + if (j.m_value.number_integer >= -24) + { + v.push_back(static_cast(0x20 + positive_number)); + } + else if (positive_number <= UINT8_MAX) + { + // int 8 + v.push_back(0x38); + add_to_vector(v, 1, positive_number); + } + else if (positive_number <= UINT16_MAX) + { + // int 16 + v.push_back(0x39); + add_to_vector(v, 2, positive_number); + } + else if (positive_number <= UINT32_MAX) + { + // int 32 + v.push_back(0x3a); + add_to_vector(v, 4, positive_number); + } + else + { + // int 64 + v.push_back(0x3b); + add_to_vector(v, 8, positive_number); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= 0x17) + { + v.push_back(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= 0xff) + { + v.push_back(0x18); + // one-byte uint8_t + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffff) + { + v.push_back(0x19); + // two-byte uint16_t + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffffffff) + { + v.push_back(0x1a); + // four-byte uint32_t + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffffffffffffffff) + { + v.push_back(0x1b); + // eight-byte uint64_t + add_to_vector(v, 8, j.m_value.number_unsigned); + } + break; + } + + case value_t::number_float: + { + // Double-Precision Float + v.push_back(0xfb); + const uint8_t* helper = reinterpret_cast(&(j.m_value.number_float)); + for (size_t i = 0; i < 8; ++i) + { + v.push_back(helper[7 - i]); + } + break; + } + + case value_t::string: + { + const auto N = j.m_value.string->size(); + if (N <= 0x17) + { + v.push_back(0x60 + N); // 1 byte for string + size + } + else if (N <= 0xff) + { + v.push_back(0x78); // one-byte uint8_t for N + add_to_vector(v, 1, N); + } + else if (N <= 0xffff) + { + v.push_back(0x79); // two-byte uint16_t for N + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + v.push_back(0x7a); // four-byte uint32_t for N + add_to_vector(v, 4, N); + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0x7b); // eight-byte uint64_t for N + add_to_vector(v, 8, N); + } + // LCOV_EXCL_STOP + + // append string + std::copy(j.m_value.string->begin(), j.m_value.string->end(), + std::back_inserter(v)); + break; + } + + case value_t::array: + { + const auto N = j.m_value.array->size(); + if (N <= 0x17) + { + v.push_back(0x80 + N); // 1 byte for array + size + } + else if (N <= 0xff) + { + v.push_back(0x98); // one-byte uint8_t for N + add_to_vector(v, 1, N); + } + else if (N <= 0xffff) + { + v.push_back(0x99); // two-byte uint16_t for N + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + v.push_back(0x9a); // four-byte uint32_t for N + add_to_vector(v, 4, N); + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0x9b); // eight-byte uint64_t for N + add_to_vector(v, 8, N); + } + // LCOV_EXCL_STOP + + // append each element + for (const auto& el : *j.m_value.array) + { + to_cbor_internal(el, v); + } + break; + } + + case value_t::object: + { + const auto N = j.m_value.object->size(); + if (N <= 0x17) + { + v.push_back(0xa0 + N); // 1 byte for object + size + } + else if (N <= 0xff) + { + v.push_back(0xb8); + add_to_vector(v, 1, N); // one-byte uint8_t for N + } + else if (N <= 0xffff) + { + v.push_back(0xb9); + add_to_vector(v, 2, N); // two-byte uint16_t for N + } + else if (N <= 0xffffffff) + { + v.push_back(0xba); + add_to_vector(v, 4, N); // four-byte uint32_t for N + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0xbb); + add_to_vector(v, 8, N); // eight-byte uint64_t for N + } + // LCOV_EXCL_STOP + + // append each element + for (const auto& el : *j.m_value.object) + { + to_cbor_internal(el.first, v); + to_cbor_internal(el.second, v); + } + break; + } + + default: + { + break; + } + } + } + + + /* + @brief checks if given lengths do not exceed the size of a given vector + + To secure the access to the byte vector during CBOR/MessagePack + deserialization, bytes are copied from the vector into buffers. This + function checks if the number of bytes to copy (@a len) does not exceed the + size @s size of the vector. Additionally, an @a offset is given from where + to start reading the bytes. + + This function checks whether reading the bytes is safe; that is, offset is a + valid index in the vector, offset+len + + @param[in] size size of the byte vector + @param[in] len number of bytes to read + @param[in] offset offset where to start reading + + vec: x x x x x X X X X X + ^ ^ ^ + 0 offset len + + @throws out_of_range if `len > v.size()` + */ + static void check_length(const size_t size, const size_t len, const size_t offset) + { + // simple case: requested length is greater than the vector's length + if (len > size or offset > size) + { + throw std::out_of_range("len out of range"); + } + + // second case: adding offset would result in overflow + if ((size > (std::numeric_limits::max() - offset))) + { + throw std::out_of_range("len+offset out of range"); + } + + // last case: reading past the end of the vector + if (len + offset > size) + { + throw std::out_of_range("len+offset out of range"); + } + } + + /*! + @brief create a JSON value from a given MessagePack vector + + @param[in] v MessagePack serialization + @param[in] idx byte index to start reading from @a v + + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from MessagePack were + used in the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @sa https://github.com/msgpack/msgpack/blob/master/spec.md + */ + static basic_json from_msgpack_internal(const std::vector& v, size_t& idx) + { + // make sure reading 1 byte is safe + check_length(v.size(), 1, idx); + + // store and increment index + const size_t current_idx = idx++; + + if (v[current_idx] <= 0xbf) + { + if (v[current_idx] <= 0x7f) // positive fixint + { + return v[current_idx]; + } + else if (v[current_idx] <= 0x8f) // fixmap + { + basic_json result = value_t::object; + const size_t len = v[current_idx] & 0x0f; + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + else if (v[current_idx] <= 0x9f) // fixarray + { + basic_json result = value_t::array; + const size_t len = v[current_idx] & 0x0f; + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + else // fixstr + { + const size_t len = v[current_idx] & 0x1f; + const size_t offset = current_idx + 1; + idx += len; // skip content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + } + else if (v[current_idx] >= 0xe0) // negative fixint + { + return static_cast(v[current_idx]); + } + else + { + switch (v[current_idx]) + { + case 0xc0: // nil + { + return value_t::null; + } + + case 0xc2: // false + { + return false; + } + + case 0xc3: // true + { + return true; + } + + case 0xca: // float 32 + { + // copy bytes in reverse order into the double variable + check_length(v.size(), sizeof(float), 1); + float res; + for (size_t byte = 0; byte < sizeof(float); ++byte) + { + reinterpret_cast(&res)[sizeof(float) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(float); // skip content bytes + return res; + } + + case 0xcb: // float 64 + { + // copy bytes in reverse order into the double variable + check_length(v.size(), sizeof(double), 1); + double res; + for (size_t byte = 0; byte < sizeof(double); ++byte) + { + reinterpret_cast(&res)[sizeof(double) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(double); // skip content bytes + return res; + } + + case 0xcc: // uint 8 + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0xcd: // uint 16 + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0xce: // uint 32 + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0xcf: // uint 64 + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd0: // int 8 + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0xd1: // int 16 + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd2: // int 32 + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd3: // int 64 + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd9: // str 8 + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 2; + idx += len + 1; // skip size byte + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xda: // str 16 + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 3; + idx += len + 2; // skip 2 size bytes + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xdb: // str 32 + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 5; + idx += len + 4; // skip 4 size bytes + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xdc: // array 16 + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + + case 0xdd: // array 32 + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + + case 0xde: // map 16 + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + + case 0xdf: // map 32 + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + + default: + { + throw std::invalid_argument("error parsing a msgpack @ " + std::to_string(current_idx) + ": " + std::to_string(static_cast(v[current_idx]))); + } + } + } + } + + /*! + @brief create a JSON value from a given CBOR vector + + @param[in] v CBOR serialization + @param[in] idx byte index to start reading from @a v + + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from CBOR were used in + the given vector @a v or if the input is not valid CBOR + @throw std::out_of_range if the given vector ends prematurely + + @sa https://tools.ietf.org/html/rfc7049 + */ + static basic_json from_cbor_internal(const std::vector& v, size_t& idx) + { + // store and increment index + const size_t current_idx = idx++; + + switch (v.at(current_idx)) + { + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + { + return v[current_idx]; + } + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0x1a: // Unsigned integer (four-byte uint32_t follows) + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0x1b: // Unsigned integer (eight-byte uint64_t follows) + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2a: + case 0x2b: + case 0x2c: + case 0x2d: + case 0x2e: + case 0x2f: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + { + return static_cast(0x20 - 1 - v[current_idx]); + } + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + idx += 1; // skip content byte + // must be uint8_t ! + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + idx += 2; // skip 2 content bytes + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x3a: // Negative integer -1-n (four-byte uint32_t follows) + { + idx += 4; // skip 4 content bytes + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x3b: // Negative integer -1-n (eight-byte uint64_t follows) + { + idx += 8; // skip 8 content bytes + return static_cast(-1) - static_cast(get_from_vector(v, current_idx)); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6a: + case 0x6b: + case 0x6c: + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + const auto len = static_cast(v[current_idx] - 0x60); + const size_t offset = current_idx + 1; + idx += len; // skip content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 2; + idx += len + 1; // skip size byte + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 3; + idx += len + 2; // skip 2 size bytes + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7a: // UTF-8 string (four-byte uint32_t for n follow) + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 5; + idx += len + 4; // skip 4 size bytes + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow) + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 9; + idx += len + 8; // skip 8 size bytes + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7f: // UTF-8 string (indefinite length) + { + std::string result; + while (v.at(idx) != 0xff) + { + string_t s = from_cbor_internal(v, idx); + result += s; + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8a: + case 0x8b: + case 0x8c: + case 0x8d: + case 0x8e: + case 0x8f: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + { + basic_json result = value_t::array; + const auto len = static_cast(v[current_idx] - 0x80); + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x98: // array (one-byte uint8_t for n follows) + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 1; // skip 1 size byte + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 2; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9a: // array (four-byte uint32_t for n follow) + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9b: // array (eight-byte uint64_t for n follow) + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 8; // skip 8 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9f: // array (indefinite length) + { + basic_json result = value_t::array; + while (v.at(idx) != 0xff) + { + result.push_back(from_cbor_internal(v, idx)); + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + // map (0x00..0x17 pairs of data items follow) + case 0xa0: + case 0xa1: + case 0xa2: + case 0xa3: + case 0xa4: + case 0xa5: + case 0xa6: + case 0xa7: + case 0xa8: + case 0xa9: + case 0xaa: + case 0xab: + case 0xac: + case 0xad: + case 0xae: + case 0xaf: + case 0xb0: + case 0xb1: + case 0xb2: + case 0xb3: + case 0xb4: + case 0xb5: + case 0xb6: + case 0xb7: + { + basic_json result = value_t::object; + const auto len = static_cast(v[current_idx] - 0xa0); + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xb8: // map (one-byte uint8_t for n follows) + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 1; // skip 1 size byte + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xb9: // map (two-byte uint16_t for n follow) + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xba: // map (four-byte uint32_t for n follow) + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xbb: // map (eight-byte uint64_t for n follow) + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 8; // skip 8 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xbf: // map (indefinite length) + { + basic_json result = value_t::object; + while (v.at(idx) != 0xff) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + case 0xf4: // false + { + return false; + } + + case 0xf5: // true + { + return true; + } + + case 0xf6: // null + { + return value_t::null; + } + + case 0xf9: // Half-Precision Float (two-byte IEEE 754) + { + check_length(v.size(), 2, 1); + idx += 2; // skip two content bytes + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added to + // IEEE 754 in 2008, today's programming platforms often still + // only have limited support for them. It is very easy to + // include at least decoding support for them even without such + // support. An example of a small decoder for half-precision + // floating-point numbers in the C language is shown in Fig. 3. + const int half = (v[current_idx + 1] << 8) + v[current_idx + 2]; + const int exp = (half >> 10) & 0x1f; + const int mant = half & 0x3ff; + double val; + if (exp == 0) + { + val = std::ldexp(mant, -24); + } + else if (exp != 31) + { + val = std::ldexp(mant + 1024, exp - 25); + } + else + { + val = mant == 0 ? INFINITY : NAN; + } + return half & 0x8000 ? -val : val; + } + + case 0xfa: // Single-Precision Float (four-byte IEEE 754) + { + // copy bytes in reverse order into the float variable + check_length(v.size(), sizeof(float), 1); + float res; + for (size_t byte = 0; byte < sizeof(float); ++byte) + { + reinterpret_cast(&res)[sizeof(float) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(float); // skip content bytes + return res; + } + + case 0xfb: // Double-Precision Float (eight-byte IEEE 754) + { + check_length(v.size(), sizeof(double), 1); + // copy bytes in reverse order into the double variable + double res; + for (size_t byte = 0; byte < sizeof(double); ++byte) + { + reinterpret_cast(&res)[sizeof(double) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(double); // skip content bytes + return res; + } + + default: // anything else (0xFF is handled inside the other types) + { + throw std::invalid_argument("error parsing a CBOR @ " + std::to_string(current_idx) + ": " + std::to_string(static_cast(v[current_idx]))); + } + } + } + + public: + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the MessagePack + serialization format. MessagePack is a binary serialization format which + aims to be more compact than JSON itself, yet more efficient to parse. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in MessagePack format.,to_msgpack} + + @sa http://msgpack.org + @sa @ref from_msgpack(const std::vector&) for the analogous + deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + */ + static std::vector to_msgpack(const basic_json& j) + { + std::vector result; + to_msgpack_internal(j, result); + return result; + } + + /*! + @brief create a JSON value from a byte vector in MessagePack format + + Deserializes a given byte vector @a v to a JSON value using the MessagePack + serialization format. + + @param[in] v a byte vector in MessagePack format + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from MessagePack were + used in the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @complexity Linear in the size of the byte vector @a v. + + @liveexample{The example shows the deserialization of a byte vector in + MessagePack format to a JSON value.,from_msgpack} + + @sa http://msgpack.org + @sa @ref to_msgpack(const basic_json&) for the analogous serialization + @sa @ref from_cbor(const std::vector&) for the related CBOR format + */ + static basic_json from_msgpack(const std::vector& v) + { + size_t i = 0; + return from_msgpack_internal(v, i); + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the CBOR (Concise + Binary Object Representation) serialization format. CBOR is a binary + serialization format which aims to be more compact than JSON itself, yet + more efficient to parse. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in CBOR format.,to_cbor} + + @sa http://cbor.io + @sa @ref from_cbor(const std::vector&) for the analogous + deserialization + @sa @ref to_msgpack(const basic_json& for the related MessagePack format + */ + static std::vector to_cbor(const basic_json& j) + { + std::vector result; + to_cbor_internal(j, result); + return result; + } + + /*! + @brief create a JSON value from a byte vector in CBOR format + + Deserializes a given byte vector @a v to a JSON value using the CBOR + (Concise Binary Object Representation) serialization format. + + @param[in] v a byte vector in CBOR format + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from CBOR were used in + the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @complexity Linear in the size of the byte vector @a v. + + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} + + @sa http://cbor.io + @sa @ref to_cbor(const basic_json&) for the analogous serialization + @sa @ref from_msgpack(const std::vector&) for the related + MessagePack format + */ + static basic_json from_cbor(const std::vector& v) + { + size_t i = 0; + return from_cbor_internal(v, i); + } + + /// @} + + private: + /////////////////////////// + // convenience functions // + /////////////////////////// + + /*! + @brief return the type as string + + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. + + @return basically a string representation of a the @a m_type member + + @complexity Constant. + + @since version 1.0.0 + */ + std::string type_name() const + { + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + + /*! + @brief calculates the extra space to escape a JSON string + + @param[in] s the string to escape + @return the number of characters required to escape string @a s + + @complexity Linear in the length of string @a s. + */ + static std::size_t extra_space(const string_t& s) noexcept + { + return std::accumulate(s.begin(), s.end(), size_t{}, + [](size_t res, typename string_t::value_type c) + { + switch (c) + { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + { + // from c (1 byte) to \x (2 bytes) + return res + 1; + } + + default: + { + if (c >= 0x00 and c <= 0x1f) + { + // from c (1 byte) to \uxxxx (6 bytes) + return res + 5; + } + else + { + return res; + } + } + } + }); + } + + /*! + @brief escape a string + + Escape a string by replacing certain special characters by a sequence of + an escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. + + @param[in] s the string to escape + @return the escaped string + + @complexity Linear in the length of string @a s. + */ + static string_t escape_string(const string_t& s) + { + const auto space = extra_space(s); + if (space == 0) + { + return s; + } + + // create a result string of necessary size + string_t result(s.size() + space, '\\'); + std::size_t pos = 0; + + for (const auto& c : s) + { + switch (c) + { + // quotation mark (0x22) + case '"': + { + result[pos + 1] = '"'; + pos += 2; + break; + } + + // reverse solidus (0x5c) + case '\\': + { + // nothing to change + pos += 2; + break; + } + + // backspace (0x08) + case '\b': + { + result[pos + 1] = 'b'; + pos += 2; + break; + } + + // formfeed (0x0c) + case '\f': + { + result[pos + 1] = 'f'; + pos += 2; + break; + } + + // newline (0x0a) + case '\n': + { + result[pos + 1] = 'n'; + pos += 2; + break; + } + + // carriage return (0x0d) + case '\r': + { + result[pos + 1] = 'r'; + pos += 2; + break; + } + + // horizontal tab (0x09) + case '\t': + { + result[pos + 1] = 't'; + pos += 2; + break; + } + + default: + { + if (c >= 0x00 and c <= 0x1f) + { + // convert a number 0..15 to its hex representation + // (0..f) + static const char hexify[16] = + { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + // print character c as \uxxxx + for (const char m : + { 'u', '0', '0', hexify[c >> 4], hexify[c & 0x0f] + }) + { + result[++pos] = m; + } + + ++pos; + } + else + { + // all other characters are added as-is + result[pos++] = c; + } + break; + } + } + } + + return result; + } + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. Note that + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[out] o stream to write to + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(std::ostream& o, + const bool pretty_print, + const unsigned int indent_step, + const unsigned int current_indent = 0) const + { + // variable to hold indentation for recursive calls + unsigned int new_indent = current_indent; + + switch (m_type) + { + case value_t::object: + { + if (m_value.object->empty()) + { + o << "{}"; + return; + } + + o << "{"; + + // increase indentation + if (pretty_print) + { + new_indent += indent_step; + o << "\n"; + } + + for (auto i = m_value.object->cbegin(); i != m_value.object->cend(); ++i) + { + if (i != m_value.object->cbegin()) + { + o << (pretty_print ? ",\n" : ","); + } + o << string_t(new_indent, ' ') << "\"" + << escape_string(i->first) << "\":" + << (pretty_print ? " " : ""); + i->second.dump(o, pretty_print, indent_step, new_indent); + } + + // decrease indentation + if (pretty_print) + { + new_indent -= indent_step; + o << "\n"; + } + + o << string_t(new_indent, ' ') + "}"; + return; + } + + case value_t::array: + { + if (m_value.array->empty()) + { + o << "[]"; + return; + } + + o << "["; + + // increase indentation + if (pretty_print) + { + new_indent += indent_step; + o << "\n"; + } + + for (auto i = m_value.array->cbegin(); i != m_value.array->cend(); ++i) + { + if (i != m_value.array->cbegin()) + { + o << (pretty_print ? ",\n" : ","); + } + o << string_t(new_indent, ' '); + i->dump(o, pretty_print, indent_step, new_indent); + } + + // decrease indentation + if (pretty_print) + { + new_indent -= indent_step; + o << "\n"; + } + + o << string_t(new_indent, ' ') << "]"; + return; + } + + case value_t::string: + { + o << string_t("\"") << escape_string(*m_value.string) << "\""; + return; + } + + case value_t::boolean: + { + o << (m_value.boolean ? "true" : "false"); + return; + } + + case value_t::number_integer: + { + o << m_value.number_integer; + return; + } + + case value_t::number_unsigned: + { + o << m_value.number_unsigned; + return; + } + + case value_t::number_float: + { + if (m_value.number_float == 0) + { + // special case for zero to get "0.0"/"-0.0" + o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0"); + } + else + { + o << m_value.number_float; + } + return; + } + + case value_t::discarded: + { + o << ""; + return; + } + + case value_t::null: + { + o << "null"; + return; + } + } + } + + private: + ////////////////////// + // member variables // + ////////////////////// + + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + + + private: + /////////////// + // iterators // + /////////////// + + /*! + @brief an iterator for primitive JSON types + + This class models an iterator for primitive JSON types (boolean, number, + string). It's only purpose is to allow the iterator/const_iterator classes + to "iterate" over primitive values. Internally, the iterator is modeled by + a `difference_type` variable. Value begin_value (`0`) models the begin, + end_value (`1`) models past the end. + */ + class primitive_iterator_t + { + public: + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return (m_it == begin_value); + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return (m_it == end_value); + } + + /// return reference to the value to change and compare + operator difference_type& () noexcept + { + return m_it; + } + + /// return value to compare + constexpr operator difference_type () const noexcept + { + return m_it; + } + + private: + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + /// iterator as signed integer type + difference_type m_it = std::numeric_limits::denorm_min(); + }; + + /*! + @brief an iterator value + + @note This structure could easily be a union, but MSVC currently does not + allow unions members with complex constructors, see + https://github.com/nlohmann/json/pull/105. + */ + struct internal_iterator + { + /// iterator for JSON objects + typename object_t::iterator object_iterator; + /// iterator for JSON arrays + typename array_t::iterator array_iterator; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator; + + /// create an uninitialized internal_iterator + internal_iterator() noexcept + : object_iterator(), array_iterator(), primitive_iterator() + {} + }; + + /// proxy class for the iterator_wrapper functions + template + class iteration_proxy + { + private: + /// helper class for iteration + class iteration_proxy_internal + { + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + size_t array_index = 0; + + public: + explicit iteration_proxy_internal(IteratorType it) noexcept + : anchor(it) + {} + + /// dereference operator (needed for range-based for) + iteration_proxy_internal& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_internal& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// inequality operator (needed for range-based for) + bool operator!= (const iteration_proxy_internal& o) const + { + return anchor != o.anchor; + } + + /// return key of the iterator + typename basic_json::string_t key() const + { + assert(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + { + return std::to_string(array_index); + } + + // use key from the object + case value_t::object: + { + return anchor.key(); + } + + // use an empty key for all primitive types + default: + { + return ""; + } + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } + }; + + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) + : container(cont) + {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_internal begin() noexcept + { + return iteration_proxy_internal(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_internal end() noexcept + { + return iteration_proxy_internal(container.end()); + } + }; + + public: + /*! + @brief a template for a random access iterator for the @ref basic_json class + + This class implements a both iterators (iterator and const_iterator) for the + @ref basic_json class. + + @note An iterator is called *initialized* when a pointer to a JSON value + has been set (e.g., by a constructor or a copy assignment). If the + iterator is default-constructed, it is *uninitialized* and most + methods are undefined. **The library uses assertions to detect calls + on uninitialized iterators.** + + @requirement The class satisfies the following concept requirements: + - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): + The iterator that can be moved to point (forward and backward) to any + element in constant time. + + @since version 1.0.0, simplified in version 2.0.9 + */ + template + class iter_impl : public std::iterator + { + /// allow basic_json to access private members + friend class basic_json; + + // make sure U is basic_json or const basic_json + static_assert(std::is_same::value + or std::is_same::value, + "iter_impl only accepts (const) basic_json"); + + public: + /// the type of the values when the iterator is dereferenced + using value_type = typename basic_json::value_type; + /// a type to represent differences between iterators + using difference_type = typename basic_json::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename std::conditional::value, + typename basic_json::const_pointer, + typename basic_json::pointer>::type; + /// defines a reference to the type iterated over (value_type) + using reference = typename std::conditional::value, + typename basic_json::const_reference, + typename basic_json::reference>::type; + /// the category of the iterator + using iterator_category = std::bidirectional_iterator_tag; + + /// default constructor + iter_impl() = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit iter_impl(pointer object) noexcept + : m_object(object) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /* + Use operator `const_iterator` instead of `const_iterator(const iterator& + other) noexcept` to avoid two class definitions for @ref iterator and + @ref const_iterator. + + This function is only called if this class is an @ref iterator. If this + class is a @ref const_iterator this function is not called. + */ + operator const_iterator() const + { + const_iterator ret; + + if (m_object) + { + ret.m_object = m_object; + ret.m_it = m_it; + } + + return ret; + } + + /*! + @brief copy constructor + @param[in] other iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl(const iter_impl& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief copy assignment + @param[in,out] other iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(iter_impl other) noexcept( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_object, other.m_object); + std::swap(m_it, other.m_it); + return *this; + } + + private: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case basic_json::value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case basic_json::value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case basic_json::value_t::null: + { + throw std::out_of_range("cannot get value"); + } + + default: + { + if (m_it.primitive_iterator.is_begin()) + { + return *m_object; + } + else + { + throw std::out_of_range("cannot get value"); + } + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case basic_json::value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (m_it.primitive_iterator.is_begin()) + { + return m_object; + } + else + { + throw std::out_of_range("cannot get value"); + } + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator++(int) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + std::advance(m_it.object_iterator, 1); + break; + } + + case basic_json::value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator--(int) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + std::advance(m_it.object_iterator, -1); + break; + } + + case basic_json::value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator==(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (m_object != other.m_object) + { + throw std::domain_error("cannot compare iterators of different containers"); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + return (m_it.object_iterator == other.m_it.object_iterator); + } + + case basic_json::value_t::array: + { + return (m_it.array_iterator == other.m_it.array_iterator); + } + + default: + { + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator!=(const iter_impl& other) const + { + return not operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (m_object != other.m_object) + { + throw std::domain_error("cannot compare iterators of different containers"); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot compare order of object iterators"); + } + + case basic_json::value_t::array: + { + return (m_it.array_iterator < other.m_it.array_iterator); + } + + default: + { + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const iter_impl& other) const + { + return not other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const iter_impl& other) const + { + return not operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const iter_impl& other) const + { + return not operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot use offsets with object iterators"); + } + + case basic_json::value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator+(difference_type i) + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator-(difference_type i) + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const iter_impl& other) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot use offsets with object iterators"); + } + + case basic_json::value_t::array: + { + return m_it.array_iterator - other.m_it.array_iterator; + } + + default: + { + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot use operator[] for object iterators"); + } + + case basic_json::value_t::array: + { + return *std::next(m_it.array_iterator, n); + } + + case basic_json::value_t::null: + { + throw std::out_of_range("cannot get value"); + } + + default: + { + if (m_it.primitive_iterator == -n) + { + return *m_object; + } + else + { + throw std::out_of_range("cannot get value"); + } + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + typename object_t::key_type key() const + { + assert(m_object != nullptr); + + if (m_object->is_object()) + { + return m_it.object_iterator->first; + } + else + { + throw std::domain_error("cannot use key() for non-object iterators"); + } + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + private: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator m_it = internal_iterator(); + }; + + /*! + @brief a template for a reverse iterator class + + @tparam Base the base iterator type to reverse. Valid types are @ref + iterator (to create @ref reverse_iterator) and @ref const_iterator (to + create @ref const_reverse_iterator). + + @requirement The class satisfies the following concept requirements: + - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): + The iterator that can be moved to point (forward and backward) to any + element in constant time. + - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + + @since version 1.0.0 + */ + template + class json_reverse_iterator : public std::reverse_iterator + { + public: + /// shortcut to the reverse iterator adaptor + using base_iterator = std::reverse_iterator; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) + {} + + /// create reverse iterator from base class + json_reverse_iterator(const base_iterator& it) noexcept + : base_iterator(it) + {} + + /// post-increment (it++) + json_reverse_iterator operator++(int) + { + return base_iterator::operator++(1); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + base_iterator::operator++(); + return *this; + } + + /// post-decrement (it--) + json_reverse_iterator operator--(int) + { + return base_iterator::operator--(1); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + base_iterator::operator--(); + return *this; + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + base_iterator::operator+=(i); + return *this; + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return this->base() - other.base(); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + typename object_t::key_type key() const + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } + }; + + + private: + ////////////////////// + // lexer and parser // + ////////////////////// + + /*! + @brief lexical analysis + + This class organizes the lexical analysis during JSON deserialization. The + core of it is a scanner generated by [re2c](http://re2c.org) that + processes a buffer and recognizes tokens according to RFC 7159. + */ + class lexer + { + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_number, ///< a number -- use get_number() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input ///< indicating the end of the input buffer + }; + + /// the char type to use in the lexer + using lexer_char_t = unsigned char; + + /// a lexer from a buffer with given length + lexer(const lexer_char_t* buff, const size_t len) noexcept + : m_content(buff) + { + assert(m_content != nullptr); + m_start = m_cursor = m_content; + m_limit = m_content + len; + } + + /// a lexer from an input stream + explicit lexer(std::istream& s) + : m_stream(&s), m_line_buffer() + { + // immediately abort if stream is erroneous + if (s.fail()) + { + throw std::invalid_argument("stream error"); + } + + // fill buffer + fill_line_buffer(); + + // skip UTF-8 byte-order mark + if (m_line_buffer.size() >= 3 and m_line_buffer.substr(0, 3) == "\xEF\xBB\xBF") + { + m_line_buffer[0] = ' '; + m_line_buffer[1] = ' '; + m_line_buffer[2] = ' '; + } + } + + // switch off unwanted functions (due to pointer members) + lexer() = delete; + lexer(const lexer&) = delete; + lexer operator=(const lexer&) = delete; + + /*! + @brief create a string from one or two Unicode code points + + There are two cases: (1) @a codepoint1 is in the Basic Multilingual + Plane (U+0000 through U+FFFF) and @a codepoint2 is 0, or (2) + @a codepoint1 and @a codepoint2 are a UTF-16 surrogate pair to + represent a code point above U+FFFF. + + @param[in] codepoint1 the code point (can be high surrogate) + @param[in] codepoint2 the code point (can be low surrogate or 0) + + @return string representation of the code point; the length of the + result string is between 1 and 4 characters. + + @throw std::out_of_range if code point is > 0x10ffff; example: `"code + points above 0x10FFFF are invalid"` + @throw std::invalid_argument if the low surrogate is invalid; example: + `""missing or wrong low surrogate""` + + @complexity Constant. + + @see + */ + static string_t to_unicode(const std::size_t codepoint1, + const std::size_t codepoint2 = 0) + { + // calculate the code point from the given code points + std::size_t codepoint = codepoint1; + + // check if codepoint1 is a high surrogate + if (codepoint1 >= 0xD800 and codepoint1 <= 0xDBFF) + { + // check if codepoint2 is a low surrogate + if (codepoint2 >= 0xDC00 and codepoint2 <= 0xDFFF) + { + codepoint = + // high surrogate occupies the most significant 22 bits + (codepoint1 << 10) + // low surrogate occupies the least significant 15 bits + + codepoint2 + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00; + } + else + { + throw std::invalid_argument("missing or wrong low surrogate"); + } + } + + string_t result; + + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + result.append(1, static_cast(codepoint)); + } + else if (codepoint <= 0x7ff) + { + // 2-byte characters: 110xxxxx 10xxxxxx + result.append(1, static_cast(0xC0 | ((codepoint >> 6) & 0x1F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else if (codepoint <= 0xffff) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + result.append(1, static_cast(0xE0 | ((codepoint >> 12) & 0x0F))); + result.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else if (codepoint <= 0x10ffff) + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + result.append(1, static_cast(0xF0 | ((codepoint >> 18) & 0x07))); + result.append(1, static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + result.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else + { + throw std::out_of_range("code points above 0x10FFFF are invalid"); + } + + return result; + } + + /// return name of values of type token_type (only used for errors) + static std::string token_type_name(const token_type t) + { + switch (t) + { + case token_type::uninitialized: + return ""; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case token_type::value_number: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return ""; + case token_type::end_of_input: + return "end of input"; + default: + { + // catch non-enum values + return "unknown token"; // LCOV_EXCL_LINE + } + } + } + + /*! + This function implements a scanner for JSON. It is specified using + regular expressions that try to follow RFC 7159 as close as possible. + These regular expressions are then translated into a minimized + deterministic finite automaton (DFA) by the tool + [re2c](http://re2c.org). As a result, the translated code for this + function consists of a large block of code with `goto` jumps. + + @return the class of the next token read from the buffer + + @complexity Linear in the length of the input.\n + + Proposition: The loop below will always terminate for finite input.\n + + Proof (by contradiction): Assume a finite input. To loop forever, the + loop must never hit code with a `break` statement. The only code + snippets without a `break` statement are the continue statements for + whitespace and byte-order-marks. To loop forever, the input must be an + infinite sequence of whitespace or byte-order-marks. This contradicts + the assumption of finite input, q.e.d. + */ + token_type scan() + { + while (true) + { + // pointer for backtracking information + m_marker = nullptr; + + // remember the begin of the token + m_start = m_cursor; + assert(m_start != nullptr); + + + { + lexer_char_t yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = + { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 32, 32, 0, 0, 32, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 160, 128, 0, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + if ((m_limit - m_cursor) < 5) + { + fill_line_buffer(5); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yybm[0 + yych] & 32) + { + goto basic_json_parser_6; + } + if (yych <= '[') + { + if (yych <= '-') + { + if (yych <= '"') + { + if (yych <= 0x00) + { + goto basic_json_parser_2; + } + if (yych <= '!') + { + goto basic_json_parser_4; + } + goto basic_json_parser_9; + } + else + { + if (yych <= '+') + { + goto basic_json_parser_4; + } + if (yych <= ',') + { + goto basic_json_parser_10; + } + goto basic_json_parser_12; + } + } + else + { + if (yych <= '9') + { + if (yych <= '/') + { + goto basic_json_parser_4; + } + if (yych <= '0') + { + goto basic_json_parser_13; + } + goto basic_json_parser_15; + } + else + { + if (yych <= ':') + { + goto basic_json_parser_17; + } + if (yych <= 'Z') + { + goto basic_json_parser_4; + } + goto basic_json_parser_19; + } + } + } + else + { + if (yych <= 'n') + { + if (yych <= 'e') + { + if (yych == ']') + { + goto basic_json_parser_21; + } + goto basic_json_parser_4; + } + else + { + if (yych <= 'f') + { + goto basic_json_parser_23; + } + if (yych <= 'm') + { + goto basic_json_parser_4; + } + goto basic_json_parser_24; + } + } + else + { + if (yych <= 'z') + { + if (yych == 't') + { + goto basic_json_parser_25; + } + goto basic_json_parser_4; + } + else + { + if (yych <= '{') + { + goto basic_json_parser_26; + } + if (yych == '}') + { + goto basic_json_parser_28; + } + goto basic_json_parser_4; + } + } + } +basic_json_parser_2: + ++m_cursor; + { + last_token_type = token_type::end_of_input; + break; + } +basic_json_parser_4: + ++m_cursor; +basic_json_parser_5: + { + last_token_type = token_type::parse_error; + break; + } +basic_json_parser_6: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yybm[0 + yych] & 32) + { + goto basic_json_parser_6; + } + { + continue; + } +basic_json_parser_9: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych <= 0x1F) + { + goto basic_json_parser_5; + } + if (yych <= 0x7F) + { + goto basic_json_parser_31; + } + if (yych <= 0xC1) + { + goto basic_json_parser_5; + } + if (yych <= 0xF4) + { + goto basic_json_parser_31; + } + goto basic_json_parser_5; +basic_json_parser_10: + ++m_cursor; + { + last_token_type = token_type::value_separator; + break; + } +basic_json_parser_12: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_5; + } + if (yych <= '0') + { + goto basic_json_parser_13; + } + if (yych <= '9') + { + goto basic_json_parser_15; + } + goto basic_json_parser_5; +basic_json_parser_13: + yyaccept = 1; + yych = *(m_marker = ++m_cursor); + if (yych <= 'D') + { + if (yych == '.') + { + goto basic_json_parser_43; + } + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_44; + } + if (yych == 'e') + { + goto basic_json_parser_44; + } + } +basic_json_parser_14: + { + last_token_type = token_type::value_number; + break; + } +basic_json_parser_15: + yyaccept = 1; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + fill_line_buffer(3); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yybm[0 + yych] & 64) + { + goto basic_json_parser_15; + } + if (yych <= 'D') + { + if (yych == '.') + { + goto basic_json_parser_43; + } + goto basic_json_parser_14; + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_44; + } + if (yych == 'e') + { + goto basic_json_parser_44; + } + goto basic_json_parser_14; + } +basic_json_parser_17: + ++m_cursor; + { + last_token_type = token_type::name_separator; + break; + } +basic_json_parser_19: + ++m_cursor; + { + last_token_type = token_type::begin_array; + break; + } +basic_json_parser_21: + ++m_cursor; + { + last_token_type = token_type::end_array; + break; + } +basic_json_parser_23: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'a') + { + goto basic_json_parser_45; + } + goto basic_json_parser_5; +basic_json_parser_24: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'u') + { + goto basic_json_parser_46; + } + goto basic_json_parser_5; +basic_json_parser_25: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'r') + { + goto basic_json_parser_47; + } + goto basic_json_parser_5; +basic_json_parser_26: + ++m_cursor; + { + last_token_type = token_type::begin_object; + break; + } +basic_json_parser_28: + ++m_cursor; + { + last_token_type = token_type::end_object; + break; + } +basic_json_parser_30: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; +basic_json_parser_31: + if (yybm[0 + yych] & 128) + { + goto basic_json_parser_30; + } + if (yych <= 0xE0) + { + if (yych <= '\\') + { + if (yych <= 0x1F) + { + goto basic_json_parser_32; + } + if (yych <= '"') + { + goto basic_json_parser_33; + } + goto basic_json_parser_35; + } + else + { + if (yych <= 0xC1) + { + goto basic_json_parser_32; + } + if (yych <= 0xDF) + { + goto basic_json_parser_36; + } + goto basic_json_parser_37; + } + } + else + { + if (yych <= 0xEF) + { + if (yych == 0xED) + { + goto basic_json_parser_39; + } + goto basic_json_parser_38; + } + else + { + if (yych <= 0xF0) + { + goto basic_json_parser_40; + } + if (yych <= 0xF3) + { + goto basic_json_parser_41; + } + if (yych <= 0xF4) + { + goto basic_json_parser_42; + } + } + } +basic_json_parser_32: + m_cursor = m_marker; + if (yyaccept == 0) + { + goto basic_json_parser_5; + } + else + { + goto basic_json_parser_14; + } +basic_json_parser_33: + ++m_cursor; + { + last_token_type = token_type::value_string; + break; + } +basic_json_parser_35: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 'e') + { + if (yych <= '/') + { + if (yych == '"') + { + goto basic_json_parser_30; + } + if (yych <= '.') + { + goto basic_json_parser_32; + } + goto basic_json_parser_30; + } + else + { + if (yych <= '\\') + { + if (yych <= '[') + { + goto basic_json_parser_32; + } + goto basic_json_parser_30; + } + else + { + if (yych == 'b') + { + goto basic_json_parser_30; + } + goto basic_json_parser_32; + } + } + } + else + { + if (yych <= 'q') + { + if (yych <= 'f') + { + goto basic_json_parser_30; + } + if (yych == 'n') + { + goto basic_json_parser_30; + } + goto basic_json_parser_32; + } + else + { + if (yych <= 's') + { + if (yych <= 'r') + { + goto basic_json_parser_30; + } + goto basic_json_parser_32; + } + else + { + if (yych <= 't') + { + goto basic_json_parser_30; + } + if (yych <= 'u') + { + goto basic_json_parser_48; + } + goto basic_json_parser_32; + } + } + } +basic_json_parser_36: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x7F) + { + goto basic_json_parser_32; + } + if (yych <= 0xBF) + { + goto basic_json_parser_30; + } + goto basic_json_parser_32; +basic_json_parser_37: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x9F) + { + goto basic_json_parser_32; + } + if (yych <= 0xBF) + { + goto basic_json_parser_36; + } + goto basic_json_parser_32; +basic_json_parser_38: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x7F) + { + goto basic_json_parser_32; + } + if (yych <= 0xBF) + { + goto basic_json_parser_36; + } + goto basic_json_parser_32; +basic_json_parser_39: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x7F) + { + goto basic_json_parser_32; + } + if (yych <= 0x9F) + { + goto basic_json_parser_36; + } + goto basic_json_parser_32; +basic_json_parser_40: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x8F) + { + goto basic_json_parser_32; + } + if (yych <= 0xBF) + { + goto basic_json_parser_38; + } + goto basic_json_parser_32; +basic_json_parser_41: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x7F) + { + goto basic_json_parser_32; + } + if (yych <= 0xBF) + { + goto basic_json_parser_38; + } + goto basic_json_parser_32; +basic_json_parser_42: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x7F) + { + goto basic_json_parser_32; + } + if (yych <= 0x8F) + { + goto basic_json_parser_38; + } + goto basic_json_parser_32; +basic_json_parser_43: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych <= '9') + { + goto basic_json_parser_49; + } + goto basic_json_parser_32; +basic_json_parser_44: + yych = *++m_cursor; + if (yych <= ',') + { + if (yych == '+') + { + goto basic_json_parser_51; + } + goto basic_json_parser_32; + } + else + { + if (yych <= '-') + { + goto basic_json_parser_51; + } + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych <= '9') + { + goto basic_json_parser_52; + } + goto basic_json_parser_32; + } +basic_json_parser_45: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_54; + } + goto basic_json_parser_32; +basic_json_parser_46: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_55; + } + goto basic_json_parser_32; +basic_json_parser_47: + yych = *++m_cursor; + if (yych == 'u') + { + goto basic_json_parser_56; + } + goto basic_json_parser_32; +basic_json_parser_48: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych <= '9') + { + goto basic_json_parser_57; + } + goto basic_json_parser_32; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_57; + } + if (yych <= '`') + { + goto basic_json_parser_32; + } + if (yych <= 'f') + { + goto basic_json_parser_57; + } + goto basic_json_parser_32; + } +basic_json_parser_49: + yyaccept = 1; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + fill_line_buffer(3); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 'D') + { + if (yych <= '/') + { + goto basic_json_parser_14; + } + if (yych <= '9') + { + goto basic_json_parser_49; + } + goto basic_json_parser_14; + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_44; + } + if (yych == 'e') + { + goto basic_json_parser_44; + } + goto basic_json_parser_14; + } +basic_json_parser_51: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych >= ':') + { + goto basic_json_parser_32; + } +basic_json_parser_52: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '/') + { + goto basic_json_parser_14; + } + if (yych <= '9') + { + goto basic_json_parser_52; + } + goto basic_json_parser_14; +basic_json_parser_54: + yych = *++m_cursor; + if (yych == 's') + { + goto basic_json_parser_58; + } + goto basic_json_parser_32; +basic_json_parser_55: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_59; + } + goto basic_json_parser_32; +basic_json_parser_56: + yych = *++m_cursor; + if (yych == 'e') + { + goto basic_json_parser_61; + } + goto basic_json_parser_32; +basic_json_parser_57: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych <= '9') + { + goto basic_json_parser_63; + } + goto basic_json_parser_32; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_63; + } + if (yych <= '`') + { + goto basic_json_parser_32; + } + if (yych <= 'f') + { + goto basic_json_parser_63; + } + goto basic_json_parser_32; + } +basic_json_parser_58: + yych = *++m_cursor; + if (yych == 'e') + { + goto basic_json_parser_64; + } + goto basic_json_parser_32; +basic_json_parser_59: + ++m_cursor; + { + last_token_type = token_type::literal_null; + break; + } +basic_json_parser_61: + ++m_cursor; + { + last_token_type = token_type::literal_true; + break; + } +basic_json_parser_63: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych <= '9') + { + goto basic_json_parser_66; + } + goto basic_json_parser_32; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_66; + } + if (yych <= '`') + { + goto basic_json_parser_32; + } + if (yych <= 'f') + { + goto basic_json_parser_66; + } + goto basic_json_parser_32; + } +basic_json_parser_64: + ++m_cursor; + { + last_token_type = token_type::literal_false; + break; + } +basic_json_parser_66: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych <= '9') + { + goto basic_json_parser_30; + } + goto basic_json_parser_32; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_30; + } + if (yych <= '`') + { + goto basic_json_parser_32; + } + if (yych <= 'f') + { + goto basic_json_parser_30; + } + goto basic_json_parser_32; + } + } + + } + + return last_token_type; + } + + /*! + @brief append data from the stream to the line buffer + + This function is called by the scan() function when the end of the + buffer (`m_limit`) is reached and the `m_cursor` pointer cannot be + incremented without leaving the limits of the line buffer. Note re2c + decides when to call this function. + + If the lexer reads from contiguous storage, there is no trailing null + byte. Therefore, this function must make sure to add these padding + null bytes. + + If the lexer reads from an input stream, this function reads the next + line of the input. + + @pre + p p p p p p u u u u u x . . . . . . + ^ ^ ^ ^ + m_content m_start | m_limit + m_cursor + + @post + u u u u u x x x x x x x . . . . . . + ^ ^ ^ + | m_cursor m_limit + m_start + m_content + */ + void fill_line_buffer(size_t n = 0) + { + // if line buffer is used, m_content points to its data + assert(m_line_buffer.empty() + or m_content == reinterpret_cast(m_line_buffer.data())); + + // if line buffer is used, m_limit is set past the end of its data + assert(m_line_buffer.empty() + or m_limit == m_content + m_line_buffer.size()); + + // pointer relationships + assert(m_content <= m_start); + assert(m_start <= m_cursor); + assert(m_cursor <= m_limit); + assert(m_marker == nullptr or m_marker <= m_limit); + + // number of processed characters (p) + const size_t num_processed_chars = static_cast(m_start - m_content); + // offset for m_marker wrt. to m_start + const auto offset_marker = (m_marker == nullptr) ? 0 : m_marker - m_start; + // number of unprocessed characters (u) + const auto offset_cursor = m_cursor - m_start; + + // no stream is used or end of file is reached + if (m_stream == nullptr or m_stream->eof()) + { + // m_start may or may not be pointing into m_line_buffer at + // this point. We trust the standand library to do the right + // thing. See http://stackoverflow.com/q/28142011/266378 + m_line_buffer.assign(m_start, m_limit); + + // append n characters to make sure that there is sufficient + // space between m_cursor and m_limit + m_line_buffer.append(1, '\x00'); + if (n > 0) + { + m_line_buffer.append(n - 1, '\x01'); + } + } + else + { + // delete processed characters from line buffer + m_line_buffer.erase(0, num_processed_chars); + // read next line from input stream + m_line_buffer_tmp.clear(); + std::getline(*m_stream, m_line_buffer_tmp, '\n'); + + // add line with newline symbol to the line buffer + m_line_buffer += m_line_buffer_tmp; + m_line_buffer.push_back('\n'); + } + + // set pointers + m_content = reinterpret_cast(m_line_buffer.data()); + assert(m_content != nullptr); + m_start = m_content; + m_marker = m_start + offset_marker; + m_cursor = m_start + offset_cursor; + m_limit = m_start + m_line_buffer.size(); + } + + /// return string representation of last read token + string_t get_token_string() const + { + assert(m_start != nullptr); + return string_t(reinterpret_cast(m_start), + static_cast(m_cursor - m_start)); + } + + /*! + @brief return string value for string tokens + + The function iterates the characters between the opening and closing + quotes of the string value. The complete string is the range + [m_start,m_cursor). Consequently, we iterate from m_start+1 to + m_cursor-1. + + We differentiate two cases: + + 1. Escaped characters. In this case, a new character is constructed + according to the nature of the escape. Some escapes create new + characters (e.g., `"\\n"` is replaced by `"\n"`), some are copied + as is (e.g., `"\\\\"`). Furthermore, Unicode escapes of the shape + `"\\uxxxx"` need special care. In this case, to_unicode takes care + of the construction of the values. + 2. Unescaped characters are copied as is. + + @pre `m_cursor - m_start >= 2`, meaning the length of the last token + is at least 2 bytes which is trivially true for any string (which + consists of at least two quotes). + + " c1 c2 c3 ... " + ^ ^ + m_start m_cursor + + @complexity Linear in the length of the string.\n + + Lemma: The loop body will always terminate.\n + + Proof (by contradiction): Assume the loop body does not terminate. As + the loop body does not contain another loop, one of the called + functions must never return. The called functions are `std::strtoul` + and to_unicode. Neither function can loop forever, so the loop body + will never loop forever which contradicts the assumption that the loop + body does not terminate, q.e.d.\n + + Lemma: The loop condition for the for loop is eventually false.\n + + Proof (by contradiction): Assume the loop does not terminate. Due to + the above lemma, this can only be due to a tautological loop + condition; that is, the loop condition i < m_cursor - 1 must always be + true. Let x be the change of i for any loop iteration. Then + m_start + 1 + x < m_cursor - 1 must hold to loop indefinitely. This + can be rephrased to m_cursor - m_start - 2 > x. With the + precondition, we x <= 0, meaning that the loop condition holds + indefinitly if i is always decreased. However, observe that the value + of i is strictly increasing with each iteration, as it is incremented + by 1 in the iteration expression and never decremented inside the loop + body. Hence, the loop condition will eventually be false which + contradicts the assumption that the loop condition is a tautology, + q.e.d. + + @return string value of current token without opening and closing + quotes + @throw std::out_of_range if to_unicode fails + */ + string_t get_string() const + { + assert(m_cursor - m_start >= 2); + + string_t result; + result.reserve(static_cast(m_cursor - m_start - 2)); + + // iterate the result between the quotes + for (const lexer_char_t* i = m_start + 1; i < m_cursor - 1; ++i) + { + // find next escape character + auto e = std::find(i, m_cursor - 1, '\\'); + if (e != i) + { + // see https://github.com/nlohmann/json/issues/365#issuecomment-262874705 + for (auto k = i; k < e; k++) + { + result.push_back(static_cast(*k)); + } + i = e - 1; // -1 because of ++i + } + else + { + // processing escaped character + // read next character + ++i; + + switch (*i) + { + // the default escapes + case 't': + { + result += "\t"; + break; + } + case 'b': + { + result += "\b"; + break; + } + case 'f': + { + result += "\f"; + break; + } + case 'n': + { + result += "\n"; + break; + } + case 'r': + { + result += "\r"; + break; + } + case '\\': + { + result += "\\"; + break; + } + case '/': + { + result += "/"; + break; + } + case '"': + { + result += "\""; + break; + } + + // unicode + case 'u': + { + // get code xxxx from uxxxx + auto codepoint = std::strtoul(std::string(reinterpret_cast(i + 1), + 4).c_str(), nullptr, 16); + + // check if codepoint is a high surrogate + if (codepoint >= 0xD800 and codepoint <= 0xDBFF) + { + // make sure there is a subsequent unicode + if ((i + 6 >= m_limit) or * (i + 5) != '\\' or * (i + 6) != 'u') + { + throw std::invalid_argument("missing low surrogate"); + } + + // get code yyyy from uxxxx\uyyyy + auto codepoint2 = std::strtoul(std::string(reinterpret_cast + (i + 7), 4).c_str(), nullptr, 16); + result += to_unicode(codepoint, codepoint2); + // skip the next 10 characters (xxxx\uyyyy) + i += 10; + } + else if (codepoint >= 0xDC00 and codepoint <= 0xDFFF) + { + // we found a lone low surrogate + throw std::invalid_argument("missing high surrogate"); + } + else + { + // add unicode character(s) + result += to_unicode(codepoint); + // skip the next four characters (xxxx) + i += 4; + } + break; + } + } + } + } + + return result; + } + + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to @a + static_cast(nullptr). + + @param[in,out] endptr recieves a pointer to the first character after + the number + + @return the floating point number + */ + long double str_to_float_t(long double* /* type */, char** endptr) const + { + return std::strtold(reinterpret_cast(m_start), endptr); + } + + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to @a + static_cast(nullptr). + + @param[in,out] endptr recieves a pointer to the first character after + the number + + @return the floating point number + */ + double str_to_float_t(double* /* type */, char** endptr) const + { + return std::strtod(reinterpret_cast(m_start), endptr); + } + + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to @a + static_cast(nullptr). + + @param[in,out] endptr recieves a pointer to the first character after + the number + + @return the floating point number + */ + float str_to_float_t(float* /* type */, char** endptr) const + { + return std::strtof(reinterpret_cast(m_start), endptr); + } + + /*! + @brief return number value for number tokens + + This function translates the last token into the most appropriate + number type (either integer, unsigned integer or floating point), + which is passed back to the caller via the result parameter. + + This function parses the integer component up to the radix point or + exponent while collecting information about the 'floating point + representation', which it stores in the result parameter. If there is + no radix point or exponent, and the number can fit into a @ref + number_integer_t or @ref number_unsigned_t then it sets the result + parameter accordingly. + + If the number is a floating point number the number is then parsed + using @a std:strtod (or @a std:strtof or @a std::strtold). + + @param[out] result @ref basic_json object to receive the number, or + NAN if the conversion read past the current token. The latter case + needs to be treated by the caller function. + */ + void get_number(basic_json& result) const + { + assert(m_start != nullptr); + + const lexer::lexer_char_t* curptr = m_start; + + // accumulate the integer conversion result (unsigned for now) + number_unsigned_t value = 0; + + // maximum absolute value of the relevant integer type + number_unsigned_t max; + + // temporarily store the type to avoid unecessary bitfield access + value_t type; + + // look for sign + if (*curptr == '-') + { + type = value_t::number_integer; + max = static_cast((std::numeric_limits::max)()) + 1; + curptr++; + } + else + { + type = value_t::number_unsigned; + max = static_cast((std::numeric_limits::max)()); + } + + // count the significant figures + for (; curptr < m_cursor; curptr++) + { + // quickly skip tests if a digit + if (*curptr < '0' || *curptr > '9') + { + if (*curptr == '.') + { + // don't count '.' but change to float + type = value_t::number_float; + continue; + } + // assume exponent (if not then will fail parse): change to + // float, stop counting and record exponent details + type = value_t::number_float; + break; + } + + // skip if definitely not an integer + if (type != value_t::number_float) + { + auto digit = static_cast(*curptr - '0'); + + // overflow if value * 10 + digit > max, move terms around + // to avoid overflow in intermediate values + if (value > (max - digit) / 10) + { + // overflow + type = value_t::number_float; + } + else + { + // no overflow + value = value * 10 + digit; + } + } + } + + // save the value (if not a float) + if (type == value_t::number_unsigned) + { + result.m_value.number_unsigned = value; + } + else if (type == value_t::number_integer) + { + // invariant: if we parsed a '-', the absolute value is between + // 0 (we allow -0) and max == -INT64_MIN + assert(value >= 0); + assert(value <= max); + + if (value == max) + { + // we cannot simply negate value (== max == -INT64_MIN), + // see https://github.com/nlohmann/json/issues/389 + result.m_value.number_integer = static_cast(INT64_MIN); + } + else + { + // all other values can be negated safely + result.m_value.number_integer = -static_cast(value); + } + } + else + { + // parse with strtod + result.m_value.number_float = str_to_float_t(static_cast(nullptr), NULL); + + // replace infinity and NAN by null + if (not std::isfinite(result.m_value.number_float)) + { + type = value_t::null; + result.m_value = basic_json::json_value(); + } + } + + // save the type + result.m_type = type; + } + + private: + /// optional input stream + std::istream* m_stream = nullptr; + /// line buffer buffer for m_stream + string_t m_line_buffer {}; + /// used for filling m_line_buffer + string_t m_line_buffer_tmp {}; + /// the buffer pointer + const lexer_char_t* m_content = nullptr; + /// pointer to the beginning of the current symbol + const lexer_char_t* m_start = nullptr; + /// pointer for backtracking information + const lexer_char_t* m_marker = nullptr; + /// pointer to the current symbol + const lexer_char_t* m_cursor = nullptr; + /// pointer to the end of the buffer + const lexer_char_t* m_limit = nullptr; + /// the last token type + token_type last_token_type = token_type::end_of_input; + }; + + /*! + @brief syntax analysis + + This class implements a recursive decent parser. + */ + class parser + { + public: + /// a parser reading from a string literal + parser(const char* buff, const parser_callback_t cb = nullptr) + : callback(cb), + m_lexer(reinterpret_cast(buff), std::strlen(buff)) + {} + + /// a parser reading from an input stream + parser(std::istream& is, const parser_callback_t cb = nullptr) + : callback(cb), m_lexer(is) + {} + + /// a parser reading from an iterator range with contiguous storage + template::iterator_category, std::random_access_iterator_tag>::value + , int>::type + = 0> + parser(IteratorType first, IteratorType last, const parser_callback_t cb = nullptr) + : callback(cb), + m_lexer(reinterpret_cast(&(*first)), + static_cast(std::distance(first, last))) + {} + + /// public parser interface + basic_json parse() + { + // read first token + get_token(); + + basic_json result = parse_internal(true); + result.assert_invariant(); + + expect(lexer::token_type::end_of_input); + + // return parser result and replace it with null in case the + // top-level value was discarded by the callback function + return result.is_discarded() ? basic_json() : std::move(result); + } + + private: + /// the actual parser + basic_json parse_internal(bool keep) + { + auto result = basic_json(value_t::discarded); + + switch (last_token) + { + case lexer::token_type::begin_object: + { + if (keep and (not callback + or ((keep = callback(depth++, parse_event_t::object_start, result)) != 0))) + { + // explicitly set result to object to cope with {} + result.m_type = value_t::object; + result.m_value = value_t::object; + } + + // read next token + get_token(); + + // closing } -> we are done + if (last_token == lexer::token_type::end_object) + { + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + // no comma is expected here + unexpect(lexer::token_type::value_separator); + + // otherwise: parse key-value pairs + do + { + // ugly, but could be fixed with loop reorganization + if (last_token == lexer::token_type::value_separator) + { + get_token(); + } + + // store key + expect(lexer::token_type::value_string); + const auto key = m_lexer.get_string(); + + bool keep_tag = false; + if (keep) + { + if (callback) + { + basic_json k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } + else + { + keep_tag = true; + } + } + + // parse separator (:) + get_token(); + expect(lexer::token_type::name_separator); + + // parse and add value + get_token(); + auto value = parse_internal(keep); + if (keep and keep_tag and not value.is_discarded()) + { + result[key] = std::move(value); + } + } + while (last_token == lexer::token_type::value_separator); + + // closing } + expect(lexer::token_type::end_object); + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result = basic_json(value_t::discarded); + } + + return result; + } + + case lexer::token_type::begin_array: + { + if (keep and (not callback + or ((keep = callback(depth++, parse_event_t::array_start, result)) != 0))) + { + // explicitly set result to object to cope with [] + result.m_type = value_t::array; + result.m_value = value_t::array; + } + + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == lexer::token_type::end_array) + { + get_token(); + if (callback and not callback(--depth, parse_event_t::array_end, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + // no comma is expected here + unexpect(lexer::token_type::value_separator); + + // otherwise: parse values + do + { + // ugly, but could be fixed with loop reorganization + if (last_token == lexer::token_type::value_separator) + { + get_token(); + } + + // parse value + auto value = parse_internal(keep); + if (keep and not value.is_discarded()) + { + result.push_back(std::move(value)); + } + } + while (last_token == lexer::token_type::value_separator); + + // closing ] + expect(lexer::token_type::end_array); + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::array_end, result)) + { + result = basic_json(value_t::discarded); + } + + return result; + } + + case lexer::token_type::literal_null: + { + get_token(); + result.m_type = value_t::null; + break; + } + + case lexer::token_type::value_string: + { + const auto s = m_lexer.get_string(); + get_token(); + result = basic_json(s); + break; + } + + case lexer::token_type::literal_true: + { + get_token(); + result.m_type = value_t::boolean; + result.m_value = true; + break; + } + + case lexer::token_type::literal_false: + { + get_token(); + result.m_type = value_t::boolean; + result.m_value = false; + break; + } + + case lexer::token_type::value_number: + { + m_lexer.get_number(result); + get_token(); + break; + } + + default: + { + // the last token was unexpected + unexpect(last_token); + } + } + + if (keep and callback and not callback(depth, parse_event_t::value, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + /// get next token from lexer + typename lexer::token_type get_token() + { + last_token = m_lexer.scan(); + return last_token; + } + + void expect(typename lexer::token_type t) const + { + if (t != last_token) + { + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token_string() + + "'") : + lexer::token_type_name(last_token)); + error_msg += "; expected " + lexer::token_type_name(t); + throw std::invalid_argument(error_msg); + } + } + + void unexpect(typename lexer::token_type t) const + { + if (t == last_token) + { + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token_string() + + "'") : + lexer::token_type_name(last_token)); + throw std::invalid_argument(error_msg); + } + } + + private: + /// current level of recursion + int depth = 0; + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + typename lexer::token_type last_token = lexer::token_type::uninitialized; + /// the lexer + lexer m_lexer; + }; + + public: + /*! + @brief JSON Pointer + + A JSON pointer defines a string syntax for identifying a specific value + within a JSON document. It can be used with functions `at` and + `operator[]`. Furthermore, JSON pointers are the base for JSON patches. + + @sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + class json_pointer + { + /// allow basic_json to access private members + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the + empty string is assumed which references the whole JSON + value + + @throw std::domain_error if reference token is nonempty and does not + begin with a slash (`/`); example: `"JSON pointer must be empty or + begin with /"` + @throw std::domain_error if a tilde (`~`) is not followed by `0` + (representing `~`) or `1` (representing `/`); example: `"escape error: + ~ must be followed with 0 or 1"` + + @liveexample{The example shows the construction several valid JSON + pointers as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`., + json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const noexcept + { + return std::accumulate(reference_tokens.begin(), + reference_tokens.end(), std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + escape(b); + }); + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + private: + /// remove and return last reference pointer + std::string pop_back() + { + if (is_root()) + { + throw std::domain_error("JSON pointer has no parent"); + } + + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; + } + + /// return whether pointer points to the root document + bool is_root() const + { + return reference_tokens.empty(); + } + + json_pointer top() const + { + if (is_root()) + { + throw std::domain_error("JSON pointer has no parent"); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + */ + reference get_and_create(reference j) const + { + pointer result = &j; + + // in case no reference tokens exist, return a reference to the + // JSON value j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case value_t::array: + { + // create an entry in the array + result = &result->operator[](static_cast(std::stoi(reference_token))); + break; + } + + /* + The following code is only reached if there exists a + reference token _and_ the current value is primitive. In + this case, we have an error situation, because primitive + values may only occur as single value; that is, with an + empty list of reference tokens. + */ + default: + { + throw std::domain_error("invalid value to unflatten"); + } + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries + to create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + */ + reference get_unchecked(pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->m_type == value_t::null) + { + // check if reference token is a number + const bool nums = std::all_of(reference_token.begin(), + reference_token.end(), + [](const char x) + { + return std::isdigit(x); + }); + + // change value to array for numbers or "-" or to object + // otherwise + if (nums or reference_token == "-") + { + *ptr = value_t::array; + } + else + { + *ptr = value_t::object; + } + } + + switch (ptr->m_type) + { + case value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case value_t::array: + { + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + if (reference_token == "-") + { + // explicityly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + } + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + reference get_checked(pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" always fails the range check + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + */ + const_reference get_unchecked(const_pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" cannot be used for const access + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // use unchecked array access + ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + const_reference get_checked(const_pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" always fails the range check + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); + break; + } + + default: + { + throw std::out_of_range("unresolved reference token '" + reference_token + "'"); + } + } + } + + return *ptr; + } + + /// split the string input to reference tokens + static std::vector split(const std::string& reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (reference_string[0] != '/') + { + throw std::domain_error("JSON pointer must be empty or begin with '/'"); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + size_t slash = reference_string.find_first_of("/", 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == string::npos+1 = 0 + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = slash + 1, + // find next slash + slash = reference_string.find_first_of("/", start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (size_t pos = reference_token.find_first_of("~"); + pos != std::string::npos; + pos = reference_token.find_first_of("~", pos + 1)) + { + assert(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (pos == reference_token.size() - 1 or + (reference_token[pos + 1] != '0' and + reference_token[pos + 1] != '1')) + { + throw std::domain_error("escape error: '~' must be followed with '0' or '1'"); + } + } + + // finally, store the reference token + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + private: + /*! + @brief replace all occurrences of a substring by another string + + @param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t + @param[in] f the substring to replace with @a t + @param[in] t the string to replace @a f + + @pre The search string @a f must not be empty. + + @since version 2.0.0 + */ + static void replace_substring(std::string& s, + const std::string& f, + const std::string& t) + { + assert(not f.empty()); + + for ( + size_t pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t + pos = s.find(f, pos + t.size()) // find next occurrence of f + ); + } + + /// escape tilde and slash + static std::string escape(std::string s) + { + // escape "~"" to "~0" and "/" to "~1" + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + /// unescape tilde and slash + static void unescape(std::string& s) + { + // first transform any occurrence of the sequence '~1' to '/' + replace_substring(s, "~1", "/"); + // then transform any occurrence of the sequence '~0' to '~' + replace_substring(s, "~0", "~"); + } + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const std::string& reference_string, + const basic_json& value, + basic_json& result) + { + switch (value.m_type) + { + case value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), + element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + */ + static basic_json unflatten(const basic_json& value) + { + if (not value.is_object()) + { + throw std::domain_error("only objects can be unflattened"); + } + + basic_json result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (not element.second.is_primitive()) + { + throw std::domain_error("values in object must be primitive"); + } + + // assign value to reference pointed to by JSON pointer; Note + // that if the JSON pointer is "" (i.e., points to the whole + // value), function get_and_create returns a reference to + // result itself. An assignment will then create a primitive + // value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + private: + /// the reference tokens + std::vector reference_tokens {}; + }; + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,at_json_pointer} + + @since version 2.0.0 + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + + @since version 2.0.0 + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitve values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this funcion, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw std::out_of_range if a JSON pointer inside the patch could not + be resolved successfully in the current JSON value; example: `"key baz + not found"` + @throw invalid_argument if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.is_root()) + { + result = val; + } + else + { + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = std::stoi(last_path); + if (static_cast(idx) > parent.size()) + { + // avoid undefined behavior + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + else + { + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + } + break; + } + + default: + { + // if there exists a parent it cannot be primitive + assert(false); // LCOV_EXCL_LINE + } + } + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (it != parent.end()) + { + parent.erase(it); + } + else + { + throw std::out_of_range("key '" + last_path + "' not found"); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(static_cast(std::stoi(last_path))); + } + }; + + // type check + if (not json_patch.is_array()) + { + // a JSON patch must be an array of objects + throw std::invalid_argument("JSON patch must be an array of objects"); + } + + // iterate and apply th eoperations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json& + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (it == val.m_value.object->end()) + { + throw std::invalid_argument(error_msg + " must have member '" + member + "'"); + } + + // check if result is of type string + if (string_type and not it->second.is_string()) + { + throw std::invalid_argument(error_msg + " must have string member '" + member + "'"); + } + + // no error: return value + return it->second; + }; + + // type check + if (not val.is_object()) + { + throw std::invalid_argument("JSON patch must be an array of objects"); + } + + // collect mandatory members + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const std::string from_path = get_value("copy", "from", true);; + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + result[ptr] = result.at(from_ptr); + break; + } + + case patch_operations::test: + { + bool success = false; + try + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + catch (std::out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (not success) + { + throw std::domain_error("unsuccessful: " + val.dump()); + } + + break; + } + + case patch_operations::invalid: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + throw std::invalid_argument("operation value '" + op + "' is invalid"); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to copare from + @param[in] target JSON value to copare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + static basic_json diff(const basic_json& source, + const basic_json& target, + const std::string& path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + } + else + { + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + size_t i = 0; + while (i < source.size() and i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + std::to_string(i)}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.begin(); it != source.end(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, + {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.begin(); it != target.end(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + break; + } + } + } + + return result; + } + + /// @} +}; + + +///////////// +// presets // +///////////// + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} + + +/////////////////////// +// nonmember support // +/////////////////////// + +// specialization of std::swap, and std::hash +namespace std +{ +/*! +@brief exchanges the values of two JSON objects + +@since version 1.0.0 +*/ +template<> +inline void swap(nlohmann::json& j1, + nlohmann::json& j2) noexcept( + is_nothrow_move_constructible::value and + is_nothrow_move_assignable::value + ) +{ + j1.swap(j2); +} + +/// hash value for JSON objects +template<> +struct hash +{ + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ + std::size_t operator()(const nlohmann::json& j) const + { + // a naive hashing via the string representation + const auto& h = hash(); + return h(j.dump()); + } +}; +} + +/*! +@brief user-defined string literal for JSON values + +This operator implements a user-defined string literal for JSON objects. It +can be used by adding `"_json"` to a string literal and returns a JSON object +if no parse error occurred. + +@param[in] s a string representation of a JSON object +@param[in] n the length of string @a s +@return a JSON object + +@since version 1.0.0 +*/ +inline nlohmann::json operator "" _json(const char* s, std::size_t n) +{ + return nlohmann::json::parse(s, s + n); +} + +/*! +@brief user-defined string literal for JSON pointer + +This operator implements a user-defined string literal for JSON Pointers. It +can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer +object if no parse error occurred. + +@param[in] s a string representation of a JSON Pointer +@param[in] n the length of string @a s +@return a JSON pointer object + +@since version 2.0.0 +*/ +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) +{ + return nlohmann::json::json_pointer(std::string(s, n)); +} + +// restore GCC/clang diagnostic settings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic pop +#endif + +#endif diff --git a/ext/libnatpmp/Changelog.txt b/ext/libnatpmp/Changelog.txt new file mode 100644 index 0000000..be75a0b --- /dev/null +++ b/ext/libnatpmp/Changelog.txt @@ -0,0 +1,98 @@ +$Id: Changelog.txt,v 1.33 2013/11/26 08:47:36 nanard Exp $ + +2013/11/26: + enforce strict aliasing rules. + +2013/09/10: + small patch for MSVC >= 16 + rename win32 implementation of gettimeofday() to natpmp_gettimeofday() + +2012/08/21: + Little change in Makefile + removed warnings in testgetgateway.c + Fixed bugs in command line argumentparsing in natpmpc.c + +2011/08/07: + Patch to build on debian/kFreeBSD. + +2011/07/15: + Put 3 clauses BSD licence at the top of source files. + +2011/06/18: + --no-undefined => -Wl,--no-undefined + adding a natpmpc.1 man page + +2011/05/19: + Small fix in libnatpmpmodule.c thanks to Manuel Mausz + +2011/01/03: + Added an argument to initnatpmp() in order to force the gateway to be used + +2011/01/01: + fix in make install + +2010/05/21: + make install now working under MacOSX (and BSD) + +2010/04/12: + cplusplus stuff in natpmp.h + +2010/02/02: + Fixed compilation under Mac OS X + +2009/12/19: + improve and fix building under Windows. + Project files for MS Visual Studio 2008 + More simple (and working) code for Win32. + More checks in the /proc/net/route parsing. Add some comments. + +2009/08/04: + improving getgateway.c for windows + +2009/07/13: + Adding Haiku code in getgateway.c + +2009/06/04: + Adding Python module thanks to David Wu + +2009/03/10: + Trying to have windows get gateway working if not using DHCP + +2009/02/27: + dont include declspec.h if not under WIN32. + +2009/01/23: + Prefixed the libraries name with lib + +2008/10/06: + Fixed a memory leak in getdefaultgateway() (USE_SYSCTL_NET_ROUTE) + +2008/07/03: + Adding WIN32 code from Robbie Hanson + +2008/06/30: + added a Solaris implementation for getgateway(). + added a LICENCE file to the distribution + +2008/05/29: + Anonymous unions are forbidden in ANSI C. That was causing problems with + non-GCC compilers. + +2008/04/28: + introduced strnatpmperr() + improved natpmpc.c sample + make install now install the binary + +2007/12/13: + Fixed getgateway.c for working under OS X ;) + Fixed values for NATPMP_PROTOCOL_TCP and NATPMP_PROTOCOL_UDP + +2007/12/11: + Fixed getgateway.c for compilation under Mac OS X + +2007/12/01: + added some comments in .h + +2007/11/30: + implemented almost everything + diff --git a/ext/libnatpmp/JavaTest.java b/ext/libnatpmp/JavaTest.java new file mode 100644 index 0000000..0379c18 --- /dev/null +++ b/ext/libnatpmp/JavaTest.java @@ -0,0 +1,42 @@ +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; + +import fr.free.miniupnp.libnatpmp.NatPmp; +import fr.free.miniupnp.libnatpmp.NatPmpResponse; + +class JavaTest { + public static void main(String[] args) { + NatPmp natpmp = new NatPmp(); + + natpmp.sendPublicAddressRequest(); + NatPmpResponse response = new NatPmpResponse(); + + int result = -1; + do{ + result = natpmp.readNatPmpResponseOrRetry(response); + try { + Thread.sleep(4000); + } catch (InterruptedException e) { + //fallthrough + } + } while (result != 0); + + byte[] bytes = intToByteArray(response.addr); + + try { + InetAddress inetAddress = InetAddress.getByAddress(bytes); + System.out.println("Public address is " + inetAddress); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + public static final byte[] intToByteArray(int value) { + return new byte[] { + (byte)value, + (byte)(value >>> 8), + (byte)(value >>> 16), + (byte)(value >>> 24)}; + } +} diff --git a/ext/libnatpmp/LICENSE b/ext/libnatpmp/LICENSE new file mode 100644 index 0000000..7fff2c2 --- /dev/null +++ b/ext/libnatpmp/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2007-2011, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/ext/libnatpmp/Makefile b/ext/libnatpmp/Makefile new file mode 100644 index 0000000..b67b3e8 --- /dev/null +++ b/ext/libnatpmp/Makefile @@ -0,0 +1,177 @@ +# $Id: Makefile,v 1.23 2013/11/26 16:38:15 nanard Exp $ +# This Makefile is designed for use with GNU make +# libnatpmp +# (c) 2007-2013 Thomas Bernard +# http://miniupnp.free.fr/libnatpmp.html + +OS = $(shell uname -s) +CC = gcc +INSTALL = install -p +ARCH = $(shell uname -m | sed -e s/i.86/i686/) +VERSION = $(shell cat VERSION) + +ifeq ($(OS), Darwin) +JARSUFFIX=mac +endif +ifeq ($(OS), Linux) +JARSUFFIX=linux +endif +ifneq (,$(findstring WIN,$(OS))) +JARSUFFIX=win32 +endif + +# APIVERSION is used in soname +APIVERSION = 1 +#LDFLAGS = -Wl,--no-undefined +CFLAGS ?= -Os +#CFLAGS = -g -O0 +CFLAGS += -fPIC +CFLAGS += -Wall +#CFLAGS += -Wextra +CFLAGS += -DENABLE_STRNATPMPERR +#CFLAGS += -Wstrict-aliasing + +LIBOBJS = natpmp.o getgateway.o + +OBJS = $(LIBOBJS) testgetgateway.o natpmpc.o natpmp-jni.o + +STATICLIB = libnatpmp.a +ifeq ($(OS), Darwin) + SHAREDLIB = libnatpmp.dylib + JNISHAREDLIB = libjninatpmp.dylib + SONAME = $(basename $(SHAREDLIB)).$(APIVERSION).dylib + CFLAGS := -DMACOSX -D_DARWIN_C_SOURCE $(CFLAGS) + SONAMEFLAGS=-Wl,-install_name,$(JNISHAREDLIB) -dynamiclib -framework JavaVM +else +ifneq (,$(findstring WIN,$(OS))) + SHAREDLIB = natpmp.dll + JNISHAREDLIB = jninatpmp.dll + CC = i686-w64-mingw32-gcc + EXTRA_LD = -lws2_32 -lIphlpapi -Wl,--no-undefined -Wl,--enable-runtime-pseudo-reloc --Wl,kill-at +else + SHAREDLIB = libnatpmp.so + JNISHAREDLIB = libjninatpmp.so + SONAME = $(SHAREDLIB).$(APIVERSION) + SONAMEFLAGS=-Wl,-soname,$(JNISHAREDLIB) +endif +endif + +HEADERS = natpmp.h + +EXECUTABLES = testgetgateway natpmpc-shared natpmpc-static + +INSTALLPREFIX ?= $(PREFIX)/usr +INSTALLDIRINC = $(INSTALLPREFIX)/include +INSTALLDIRLIB = $(INSTALLPREFIX)/lib +INSTALLDIRBIN = $(INSTALLPREFIX)/bin + +JAVA ?= java +JAVAC ?= javac +JAVAH ?= javah +JAVAPACKAGE = fr/free/miniupnp/libnatpmp +JAVACLASSES = $(JAVAPACKAGE)/NatPmp.class $(JAVAPACKAGE)/NatPmpResponse.class $(JAVAPACKAGE)/LibraryExtractor.class $(JAVAPACKAGE)/URLUtils.class +JNIHEADERS = fr_free_miniupnp_libnatpmp_NatPmp.h + +.PHONY: all clean depend install cleaninstall installpythonmodule + +all: $(STATICLIB) $(SHAREDLIB) $(EXECUTABLES) + +pythonmodule: $(STATICLIB) libnatpmpmodule.c setup.py + python setup.py build + touch $@ + +installpythonmodule: pythonmodule + python setup.py install + +clean: + $(RM) $(OBJS) $(EXECUTABLES) $(STATICLIB) $(SHAREDLIB) $(JAVACLASSES) $(JNISHAREDLIB) + $(RM) pythonmodule + $(RM) -r build/ dist/ libraries/ + +depend: + makedepend -f$(MAKEFILE_LIST) -Y $(OBJS:.o=.c) 2>/dev/null + +install: $(HEADERS) $(STATICLIB) $(SHAREDLIB) natpmpc-shared + $(INSTALL) -d $(INSTALLDIRINC) + $(INSTALL) -m 644 $(HEADERS) $(INSTALLDIRINC) + $(INSTALL) -d $(INSTALLDIRLIB) + $(INSTALL) -m 644 $(STATICLIB) $(INSTALLDIRLIB) + $(INSTALL) -m 644 $(SHAREDLIB) $(INSTALLDIRLIB)/$(SONAME) + $(INSTALL) -d $(INSTALLDIRBIN) + $(INSTALL) -m 755 natpmpc-shared $(INSTALLDIRBIN)/natpmpc + ln -s -f $(SONAME) $(INSTALLDIRLIB)/$(SHAREDLIB) + +$(JNIHEADERS): fr/free/miniupnp/libnatpmp/NatPmp.class + $(JAVAH) -jni fr.free.miniupnp.libnatpmp.NatPmp + +%.class: %.java + $(JAVAC) -cp . $< + +$(JNISHAREDLIB): $(SHAREDLIB) $(JNIHEADERS) $(JAVACLASSES) +ifeq (,$(JAVA_HOME)) + @echo "Check your JAVA_HOME environement variable" && false +endif +ifneq (,$(findstring WIN,$(OS))) + $(CC) -m32 -D_JNI_Implementation_ -Wl,--kill-at \ + -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/win32" \ + natpmp-jni.c -shared \ + -o $(JNISHAREDLIB) -L. -lnatpmp -lws2_32 -lIphlpapi +else + $(CC) $(CFLAGS) -c -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/win32" natpmp-jni.c + $(CC) $(CFLAGS) -o $(JNISHAREDLIB) -shared $(SONAMEFLAGS) natpmp-jni.o -lc -L. -lnatpmp +endif + +jar: $(JNISHAREDLIB) + find fr -name '*.class' -print > classes.list + $(eval JNISHAREDLIBPATH := $(shell java fr.free.miniupnp.libnatpmp.LibraryExtractor)) + mkdir -p libraries/$(JNISHAREDLIBPATH) + mv $(JNISHAREDLIB) libraries/$(JNISHAREDLIBPATH)/$(JNISHAREDLIB) + jar cf natpmp_$(JARSUFFIX).jar @classes.list libraries/$(JNISHAREDLIBPATH)/$(JNISHAREDLIB) + $(RM) classes.list + +jnitest: $(JNISHAREDLIB) JavaTest.class + $(RM) libjninatpmp.so + $(JAVA) -Djna.nosys=true -cp . JavaTest + +mvn_install: + mvn install:install-file -Dfile=java/natpmp_$(JARSUFFIX).jar \ + -DgroupId=com.github \ + -DartifactId=natpmp \ + -Dversion=$(VERSION) \ + -Dpackaging=jar \ + -Dclassifier=$(JARSUFFIX) \ + -DgeneratePom=true \ + -DcreateChecksum=true + +cleaninstall: + $(RM) $(addprefix $(INSTALLDIRINC), $(HEADERS)) + $(RM) $(INSTALLDIRLIB)/$(SONAME) + $(RM) $(INSTALLDIRLIB)/$(SHAREDLIB) + $(RM) $(INSTALLDIRLIB)/$(STATICLIB) + +testgetgateway: testgetgateway.o getgateway.o + $(CC) $(LDFLAGS) -o $@ $^ $(EXTRA_LD) + +natpmpc-static: natpmpc.o $(STATICLIB) + $(CC) $(LDFLAGS) -o $@ $^ $(EXTRA_LD) + +natpmpc-shared: natpmpc.o $(SHAREDLIB) + $(CC) $(LDFLAGS) -o $@ $^ $(EXTRA_LD) + +$(STATICLIB): $(LIBOBJS) + $(AR) crs $@ $? + +$(SHAREDLIB): $(LIBOBJS) +ifeq ($(OS), Darwin) + $(CC) -dynamiclib -Wl,-install_name,$(SONAME) -o $@ $^ +else + $(CC) -shared -Wl,-soname,$(SONAME) -o $@ $^ $(EXTRA_LD) +endif + + +# DO NOT DELETE + +natpmp.o: natpmp.h getgateway.h declspec.h +getgateway.o: getgateway.h declspec.h +testgetgateway.o: getgateway.h declspec.h +natpmpc.o: natpmp.h diff --git a/ext/libnatpmp/README b/ext/libnatpmp/README new file mode 100644 index 0000000..269392d --- /dev/null +++ b/ext/libnatpmp/README @@ -0,0 +1,7 @@ +libnatpmp (c) 2007-2009 Thomas Bernard +contact : miniupnp@free.fr + +see http://miniupnp.free.fr/libnatpmp.html +or http://miniupnp.tuxfamily.org/libnatpmp.html +for some documentation and code samples. + diff --git a/ext/libnatpmp/build.bat b/ext/libnatpmp/build.bat new file mode 100644 index 0000000..2d2f27c --- /dev/null +++ b/ext/libnatpmp/build.bat @@ -0,0 +1,30 @@ +@echo Compiling with MinGW +@SET LIBS=-lws2_32 -liphlpapi + +@echo Compile getgateway +gcc -c -Wall -Os -DWIN32 -DSTATICLIB -DENABLE_STRNATPMPERR getgateway.c +gcc -c -Wall -Os -DWIN32 -DSTATICLIB -DENABLE_STRNATPMPERR testgetgateway.c +gcc -o testgetgateway getgateway.o testgetgateway.o %LIBS% +del testgetgateway.o + +@echo Compile natpmp-static: +gcc -c -Wall -Os -DWIN32 -DSTATICLIB -DENABLE_STRNATPMPERR getgateway.c +gcc -c -Wall -Os -DWIN32 -DSTATICLIB -DENABLE_STRNATPMPERR natpmp.c +gcc -c -Wall -Os -DWIN32 wingettimeofday.c +ar cr natpmp.a getgateway.o natpmp.o wingettimeofday.o +del getgateway.o natpmp.o +gcc -c -Wall -Os -DWIN32 -DSTATICLIB -DENABLE_STRNATPMPERR natpmpc.c +gcc -o natpmpc-static natpmpc.o natpmp.a %LIBS% +upx --best natpmpc-static.exe +del natpmpc.o + +@echo Create natpmp.dll: +gcc -c -Wall -Os -DWIN32 -DENABLE_STRNATPMPERR -DNATPMP_EXPORTS getgateway.c +gcc -c -Wall -Os -DWIN32 -DENABLE_STRNATPMPERR -DNATPMP_EXPORTS natpmp.c +dllwrap -k --driver-name gcc --def natpmp.def --output-def natpmp.dll.def --implib natpmp.lib -o natpmp.dll getgateway.o natpmp.o wingettimeofday.o %LIBS% + +@echo Compile natpmp-shared: +gcc -c -Wall -Os -DWIN32 -DENABLE_STRNATPMPERR -DNATPMP_EXPORTS natpmpc.c +gcc -o natpmpc-shared natpmpc.o natpmp.lib -lws2_32 +upx --best natpmpc-shared.exe +del *.o diff --git a/ext/libnatpmp/declspec.h b/ext/libnatpmp/declspec.h new file mode 100644 index 0000000..a76be02 --- /dev/null +++ b/ext/libnatpmp/declspec.h @@ -0,0 +1,21 @@ +#ifndef DECLSPEC_H_INCLUDED +#define DECLSPEC_H_INCLUDED + +#if defined(WIN32) && !defined(STATICLIB) + /* for windows dll */ + #ifdef NATPMP_EXPORTS + #define LIBSPEC __declspec(dllexport) + #else + #define LIBSPEC __declspec(dllimport) + #endif +#else + #if defined(__GNUC__) && __GNUC__ >= 4 + /* fix dynlib for OS X 10.9.2 and Apple LLVM version 5.0 */ + #define LIBSPEC __attribute__ ((visibility ("default"))) + #else + #define LIBSPEC + #endif +#endif + +#endif + diff --git a/ext/libnatpmp/fr/free/miniupnp/libnatpmp/LibraryExtractor.java b/ext/libnatpmp/fr/free/miniupnp/libnatpmp/LibraryExtractor.java new file mode 100644 index 0000000..5491d94 --- /dev/null +++ b/ext/libnatpmp/fr/free/miniupnp/libnatpmp/LibraryExtractor.java @@ -0,0 +1,238 @@ +package fr.free.miniupnp.libnatpmp; + +/** I (Leah X Schmidt) copied this code from jnaerator, because +JNAerator's extractor requires you to buy into the whole JNA +concept. + +JNAErator is +Copyright (c) 2009 Olivier Chafik, All Rights Reserved + +JNAerator 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. + +JNAerator 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 JNAerator. If not, see . + +*/ + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class LibraryExtractor { + + private static boolean libPathSet = false; + + public static String getLibraryPath(String libraryName, boolean extractAllLibraries, Class cl) { + try { + String customPath = System.getProperty("library." + libraryName); + if (customPath == null) + customPath = System.getenv(libraryName.toUpperCase() + "_LIBRARY"); + if (customPath != null) { + File f = new File(customPath); + if (!f.exists()) + System.err.println("Library file '" + customPath + "' does not exist !"); + else + return f.getAbsolutePath(); + } + //ClassLoader cl = LibraryExtractor.class.getClassLoader(); + String prefix = "(?i)" + (isWindows() ? "" : "lib") + libraryName + "[^A-Za-z_].*"; + String libsuffix = "(?i).*\\.(so|dll|dylib|jnilib)"; + //String othersuffix = "(?i).*\\.(pdb)"; + + URL sourceURL = null; + List otherURLs = new ArrayList(); + + + String arch = getCurrentOSAndArchString(); + //System.out.println("libURL = " + libURL); + List list = URLUtils.listFiles(URLUtils.getResource(cl, "libraries/" + arch)), + noArchList = URLUtils.listFiles(URLUtils.getResource(cl, "libraries/noarch")); + + Set names = new HashSet(); + for (URL url : list) { + String name = getFileName(url); + names.add(name); + } + for (URL url : noArchList) { + String name = getFileName(url); + if (names.add(name)) + list.add(url); + } + + for (File f : new File(".").listFiles()) + if (f.isFile()) + list.add(f.toURI().toURL()); + + for (URL url : list) { + String name = getFileName(url); + boolean pref = name.matches(prefix), suff = name.matches(libsuffix); + if (pref && suff) + sourceURL = url; + else //if (suff || fileName.matches(othersuffix)) + otherURLs.add(url); + } + List files = new ArrayList(); + if (extractAllLibraries) { + for (URL url : otherURLs) + files.add(extract(url)); + } + + if (System.getProperty("javawebstart.version") != null) { + if (isWindows()) { + //File f = new File("c:\\Windows\\" + (Platform.is64Bit() ? "SysWOW64\\" : "System32\\") + libraryName + ".dll"); + File f = new File("c:\\Windows\\" + "System32\\" + libraryName + ".dll"); + if (f.exists()) + return f.toString(); + } else if (isMac()) { + File f = new File("/System/Library/Frameworks/" + libraryName + ".framework/" + libraryName); + if (f.exists()) + return f.toString(); + } + } + + if (sourceURL == null) + return libraryName; + else { + File file = extract(sourceURL); + files.add(file); + + int lastSize; + do { + lastSize = files.size(); + for (Iterator it = files.iterator(); it.hasNext();) { + File f = it.next(); + if (!f.getName().matches(libsuffix)) + continue; + + try { + System.load(f.toString()); + it.remove(); + } catch (Throwable ex) { + System.err.println("Loading " + f.getName() + " failed (" + ex + ")"); + } + } + } while (files.size() < lastSize); + + return file.getCanonicalPath(); + } + } catch (Throwable ex) { + System.err.println("ERROR: Failed to extract library " + libraryName); + ex.printStackTrace(); + return libraryName; + } + } + + public static final boolean isWindows() { + String osName = System.getProperty("os.name"); + return osName.startsWith("Windows"); + } + + public static final boolean isMac() { + String osName = System.getProperty("os.name"); + return osName.startsWith("Mac") || osName.startsWith("Darwin"); + } + + //this code is from JNA, but JNA has a fallback to some native + //stuff in case this doesn't work. Since sun.arch.data.model is + //defined for Sun and IBM, this should work nearly everywhere. + public static final boolean is64Bit() { + String model = System.getProperty("sun.arch.data.model", + System.getProperty("com.ibm.vm.bitmode")); + if (model != null) { + return "64".equals(model); + } + String arch = System.getProperty("os.arch").toLowerCase(); + if ("x86_64".equals(arch) + || "ia64".equals(arch) + || "ppc64".equals(arch) + || "sparcv9".equals(arch) + || "amd64".equals(arch)) { + return true; + } + + return false; + } + + public static String getCurrentOSAndArchString() { + String os = System.getProperty("os.name"), arch = System.getProperty("os.arch"); + if (os.equals("Mac OS X")) { + os = "darwin"; + arch = "fat"; + } else if (os.startsWith("Windows")) { + return "win" + (is64Bit() ? "64" : "32"); + } else if (os.matches("SunOS|Solaris")) + os = "solaris"; + return os + "-" + arch; + } + + private static File extract(URL url) throws IOException { + File localFile; + if ("file".equals(url.getProtocol())) + localFile = new File(URLDecoder.decode(url.getFile(), "UTF-8")); + else { + File f = new File(System.getProperty("user.home")); + f = new File(f, ".jnaerator"); + f = new File(f, "extractedLibraries"); + if (!f.exists()) + f.mkdirs(); + + if (!libPathSet) { + String path = System.getProperty("java.library.path"); + if (path == null) { + System.setProperty("java.library.path", f.toString()); + } else { + System.setProperty("java.library.path", path + ":" + f); + } + + libPathSet = true; + } + localFile = new File(f, new File(url.getFile()).getName()); + URLConnection c = url.openConnection(); + if (localFile.exists() && localFile.lastModified() > c.getLastModified()) { + c.getInputStream().close(); + } else { + System.out.println("Extracting " + url); + InputStream in = c.getInputStream(); + OutputStream out = new FileOutputStream(localFile); + int len; + byte[] b = new byte[1024]; + while ((len = in.read(b)) > 0) + out.write(b, 0, len); + out.close(); + in.close(); + } + } + return localFile; + } + + private static String getFileName(URL url) { + return new File(url.getFile()).getName(); + } + + public static void main(String[] args) { + System.out.println(getCurrentOSAndArchString()); + } +} \ No newline at end of file diff --git a/ext/libnatpmp/fr/free/miniupnp/libnatpmp/NatPmp.java b/ext/libnatpmp/fr/free/miniupnp/libnatpmp/NatPmp.java new file mode 100644 index 0000000..2f1ddd3 --- /dev/null +++ b/ext/libnatpmp/fr/free/miniupnp/libnatpmp/NatPmp.java @@ -0,0 +1,50 @@ +package fr.free.miniupnp.libnatpmp; + +import java.nio.ByteBuffer; + + +public class NatPmp { + private static final String JNA_LIBRARY_NAME = LibraryExtractor.getLibraryPath("jninatpmp", true, NatPmp.class); + + static { + String s = JNA_LIBRARY_NAME; + startup(); + } + + public ByteBuffer natpmp; + + public NatPmp() { + init(0, 0); + } + + public NatPmp(int forcedgw) { + init(1, forcedgw); + } + + /** Cleans up the native resources used by this object. + Attempting to use the object after this has been called + will lead to crashes.*/ + public void dispose() { + free(); + } + + + protected void finalize() { + if (natpmp != null) + free(); + } + + private native void init(int forcegw, int forcedgw); + private native void free(); + + private static native void startup(); + + public native int sendPublicAddressRequest(); + public native int sendNewPortMappingRequest(int protocol, int privateport, int publicport, int lifetime); + + //returns a number of milliseconds, in accordance with Java convention + public native long getNatPmpRequestTimeout(); + + public native int readNatPmpResponseOrRetry(NatPmpResponse response); + +} diff --git a/ext/libnatpmp/fr/free/miniupnp/libnatpmp/NatPmpResponse.java b/ext/libnatpmp/fr/free/miniupnp/libnatpmp/NatPmpResponse.java new file mode 100644 index 0000000..35c87ea --- /dev/null +++ b/ext/libnatpmp/fr/free/miniupnp/libnatpmp/NatPmpResponse.java @@ -0,0 +1,28 @@ +package fr.free.miniupnp.libnatpmp; + +public class NatPmpResponse { + + public static final int TYPE_PUBLICADDRESS=0; + public static final int TYPE_UDPPORTMAPPING=1; + public static final int TYPE_TCPPORTMAPPING=2; + + /** see TYPE_* constants */ + public short type; + /** NAT-PMP response code */ + public short resultcode; + /** milliseconds since start of epoch */ + public long epoch; + + /** only defined if type == 0*/ + public int addr; + + /** only defined if type != 0 */ + public int privateport; + + /** only defined if type != 0 */ + public int mappedpublicport; + + /** only defined if type != 0 */ + public long lifetime; //milliseconds + +} \ No newline at end of file diff --git a/ext/libnatpmp/fr/free/miniupnp/libnatpmp/URLUtils.java b/ext/libnatpmp/fr/free/miniupnp/libnatpmp/URLUtils.java new file mode 100644 index 0000000..5b419ab --- /dev/null +++ b/ext/libnatpmp/fr/free/miniupnp/libnatpmp/URLUtils.java @@ -0,0 +1,81 @@ +package fr.free.miniupnp.libnatpmp; + +/** I (Leah X Schmidt) copied this code from jnaerator, because +JNAerator's extractor requires you to buy into the whole JNA +concept. + +JNAErator is +Copyright (c) 2009 Olivier Chafik, All Rights Reserved + +JNAerator 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. + +JNAerator 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 JNAerator. If not, see . + +*/ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +public class URLUtils { + + public static URL getResource(Class cl, String path) throws IOException { + String clp = cl.getName().replace('.', '/') + ".class"; + URL clu = cl.getClassLoader().getResource(clp); + String s = clu.toString(); + if (s.endsWith(clp)) + return new URL(s.substring(0, s.length() - clp.length()) + path); + + if (s.startsWith("jar:")) { + String[] ss = s.split("!"); + return new URL(ss[1] + "!/" + path); + } + return null; + } + + public static List listFiles(URL directory) throws IOException { + List ret = new ArrayList(); + String s = directory.toString(); + if (s.startsWith("jar:")) { + String[] ss = s.substring("jar:".length()).split("!"); + String path = ss[1]; + URL target = new URL(ss[0]); + InputStream tin = target.openStream(); + try { + JarInputStream jin = new JarInputStream(tin); + JarEntry je; + while ((je = jin.getNextJarEntry()) != null) { + String p = "/" + je.getName(); + if (p.startsWith(path) && p.indexOf('/', path.length() + 1) < 0) + + ret.add(new URL("jar:" + target + "!" + p)); + } + } finally { + tin.close(); + } + } else if (s.startsWith("file:")) { + File f = new File(directory.getFile()); + File[] ffs = f.listFiles(); + if (ffs != null) + for (File ff : ffs) + ret.add(ff.toURI().toURL()); + } else + throw new IOException("Cannot list contents of " + directory); + + return ret; + } +} \ No newline at end of file diff --git a/ext/libnatpmp/getgateway.c b/ext/libnatpmp/getgateway.c new file mode 100644 index 0000000..f743a08 --- /dev/null +++ b/ext/libnatpmp/getgateway.c @@ -0,0 +1,573 @@ +/* $Id: getgateway.c,v 1.25 2014/04/22 10:28:57 nanard Exp $ */ +/* libnatpmp + +Copyright (c) 2007-2014, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#ifndef WIN32 +#include +#endif +#if !defined(_MSC_VER) +#include +#endif +/* There is no portable method to get the default route gateway. + * So below are four (or five ?) differents functions implementing this. + * Parsing /proc/net/route is for linux. + * sysctl is the way to access such informations on BSD systems. + * Many systems should provide route information through raw PF_ROUTE + * sockets. + * In MS Windows, default gateway is found by looking into the registry + * or by using GetBestRoute(). */ +#ifdef __linux__ +#define USE_PROC_NET_ROUTE +#undef USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +#endif + +#if defined(BSD) || defined(__FreeBSD_kernel__) +#undef USE_PROC_NET_ROUTE +#define USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +#include +#endif + +#ifdef __APPLE__ +#undef USE_PROC_NET_ROUTE +#undef USE_SOCKET_ROUTE +#define USE_SYSCTL_NET_ROUTE +#endif + +#if (defined(sun) && defined(__SVR4)) +#undef USE_PROC_NET_ROUTE +#define USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +#endif + +#ifdef WIN32 +#undef USE_PROC_NET_ROUTE +#undef USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +//#define USE_WIN32_CODE +#define USE_WIN32_CODE_2 +#endif + +#ifdef __CYGWIN__ +#undef USE_PROC_NET_ROUTE +#undef USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +#define USE_WIN32_CODE +#include +#include +#include +#include +#endif + +#ifdef __HAIKU__ +#include +#include +#include +#include +#define USE_HAIKU_CODE +#endif + +#ifdef USE_SYSCTL_NET_ROUTE +#include +#include +#include +#endif +#ifdef USE_SOCKET_ROUTE +#include +#include +#include +#include +#include +#endif + +#ifdef USE_WIN32_CODE +#include +#include +#define MAX_KEY_LENGTH 255 +#define MAX_VALUE_LENGTH 16383 +#endif + +#ifdef USE_WIN32_CODE_2 +#include +#include +#endif + +#include "getgateway.h" + +#ifndef WIN32 +#define SUCCESS (0) +#define FAILED (-1) +#endif + +#ifdef USE_PROC_NET_ROUTE +/* + parse /proc/net/route which is as follow : + +Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +wlan0 0001A8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 +eth0 0000FEA9 00000000 0001 0 0 0 0000FFFF 0 0 0 +wlan0 00000000 0101A8C0 0003 0 0 0 00000000 0 0 0 +eth0 00000000 00000000 0001 0 0 1000 00000000 0 0 0 + + One header line, and then one line by route by route table entry. +*/ +int getdefaultgateway(in_addr_t * addr) +{ + unsigned long d, g; + char buf[256]; + int line = 0; + FILE * f; + char * p; + f = fopen("/proc/net/route", "r"); + if(!f) + return FAILED; + while(fgets(buf, sizeof(buf), f)) { + if(line > 0) { /* skip the first line */ + p = buf; + /* skip the interface name */ + while(*p && !isspace(*p)) + p++; + while(*p && isspace(*p)) + p++; + if(sscanf(p, "%lx%lx", &d, &g)==2) { + if(d == 0 && g != 0) { /* default */ + *addr = g; + fclose(f); + return SUCCESS; + } + } + } + line++; + } + /* default route not found ! */ + if(f) + fclose(f); + return FAILED; +} +#endif /* #ifdef USE_PROC_NET_ROUTE */ + + +#ifdef USE_SYSCTL_NET_ROUTE + +#define ROUNDUP(a) \ + ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) + +int getdefaultgateway(in_addr_t * addr) +{ +#if 0 + /* net.route.0.inet.dump.0.0 ? */ + int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET, + NET_RT_DUMP, 0, 0/*tableid*/}; +#endif + /* net.route.0.inet.flags.gateway */ + int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET, + NET_RT_FLAGS, RTF_GATEWAY}; + size_t l; + char * buf, * p; + struct rt_msghdr * rt; + struct sockaddr * sa; + struct sockaddr * sa_tab[RTAX_MAX]; + int i; + int r = FAILED; + if(sysctl(mib, sizeof(mib)/sizeof(int), 0, &l, 0, 0) < 0) { + return FAILED; + } + if(l>0) { + buf = malloc(l); + if(sysctl(mib, sizeof(mib)/sizeof(int), buf, &l, 0, 0) < 0) { + free(buf); + return FAILED; + } + for(p=buf; prtm_msglen) { + rt = (struct rt_msghdr *)p; + sa = (struct sockaddr *)(rt + 1); + for(i=0; irtm_addrs & (1 << i)) { + sa_tab[i] = sa; + sa = (struct sockaddr *)((char *)sa + ROUNDUP(sa->sa_len)); + } else { + sa_tab[i] = NULL; + } + } + if( ((rt->rtm_addrs & (RTA_DST|RTA_GATEWAY)) == (RTA_DST|RTA_GATEWAY)) + && sa_tab[RTAX_DST]->sa_family == AF_INET + && sa_tab[RTAX_GATEWAY]->sa_family == AF_INET) { + if(((struct sockaddr_in *)sa_tab[RTAX_DST])->sin_addr.s_addr == 0) { + *addr = ((struct sockaddr_in *)(sa_tab[RTAX_GATEWAY]))->sin_addr.s_addr; + r = SUCCESS; + } + } + } + free(buf); + } + return r; +} +#endif /* #ifdef USE_SYSCTL_NET_ROUTE */ + + +#ifdef USE_SOCKET_ROUTE +/* Thanks to Darren Kenny for this code */ +#define NEXTADDR(w, u) \ + if (rtm_addrs & (w)) {\ + l = sizeof(struct sockaddr); memmove(cp, &(u), l); cp += l;\ + } + +#define rtm m_rtmsg.m_rtm + +struct { + struct rt_msghdr m_rtm; + char m_space[512]; +} m_rtmsg; + +int getdefaultgateway(in_addr_t *addr) +{ + int s, seq, l, rtm_addrs, i; + pid_t pid; + struct sockaddr so_dst, so_mask; + char *cp = m_rtmsg.m_space; + struct sockaddr *gate = NULL, *sa; + struct rt_msghdr *msg_hdr; + + pid = getpid(); + seq = 0; + rtm_addrs = RTA_DST | RTA_NETMASK; + + memset(&so_dst, 0, sizeof(so_dst)); + memset(&so_mask, 0, sizeof(so_mask)); + memset(&rtm, 0, sizeof(struct rt_msghdr)); + + rtm.rtm_type = RTM_GET; + rtm.rtm_flags = RTF_UP | RTF_GATEWAY; + rtm.rtm_version = RTM_VERSION; + rtm.rtm_seq = ++seq; + rtm.rtm_addrs = rtm_addrs; + + so_dst.sa_family = AF_INET; + so_mask.sa_family = AF_INET; + + NEXTADDR(RTA_DST, so_dst); + NEXTADDR(RTA_NETMASK, so_mask); + + rtm.rtm_msglen = l = cp - (char *)&m_rtmsg; + + s = socket(PF_ROUTE, SOCK_RAW, 0); + + if (write(s, (char *)&m_rtmsg, l) < 0) { + close(s); + return FAILED; + } + + do { + l = read(s, (char *)&m_rtmsg, sizeof(m_rtmsg)); + } while (l > 0 && (rtm.rtm_seq != seq || rtm.rtm_pid != pid)); + + close(s); + + msg_hdr = &rtm; + + cp = ((char *)(msg_hdr + 1)); + if (msg_hdr->rtm_addrs) { + for (i = 1; i; i <<= 1) + if (i & msg_hdr->rtm_addrs) { + sa = (struct sockaddr *)cp; + if (i == RTA_GATEWAY ) + gate = sa; + + cp += sizeof(struct sockaddr); + } + } else { + return FAILED; + } + + + if (gate != NULL ) { + *addr = ((struct sockaddr_in *)gate)->sin_addr.s_addr; + return SUCCESS; + } else { + return FAILED; + } +} +#endif /* #ifdef USE_SOCKET_ROUTE */ + +#ifdef USE_WIN32_CODE +LIBSPEC int getdefaultgateway(in_addr_t * addr) +{ + HKEY networkCardsKey; + HKEY networkCardKey; + HKEY interfacesKey; + HKEY interfaceKey; + DWORD i = 0; + DWORD numSubKeys = 0; + TCHAR keyName[MAX_KEY_LENGTH]; + DWORD keyNameLength = MAX_KEY_LENGTH; + TCHAR keyValue[MAX_VALUE_LENGTH]; + DWORD keyValueLength = MAX_VALUE_LENGTH; + DWORD keyValueType = REG_SZ; + TCHAR gatewayValue[MAX_VALUE_LENGTH]; + DWORD gatewayValueLength = MAX_VALUE_LENGTH; + DWORD gatewayValueType = REG_MULTI_SZ; + int done = 0; + + //const char * networkCardsPath = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards"; + //const char * interfacesPath = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"; +#ifdef UNICODE + LPCTSTR networkCardsPath = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards"; + LPCTSTR interfacesPath = L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"; +#define STR_SERVICENAME L"ServiceName" +#define STR_DHCPDEFAULTGATEWAY L"DhcpDefaultGateway" +#define STR_DEFAULTGATEWAY L"DefaultGateway" +#else + LPCTSTR networkCardsPath = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards"; + LPCTSTR interfacesPath = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"; +#define STR_SERVICENAME "ServiceName" +#define STR_DHCPDEFAULTGATEWAY "DhcpDefaultGateway" +#define STR_DEFAULTGATEWAY "DefaultGateway" +#endif + // The windows registry lists its primary network devices in the following location: + // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkCards + // + // Each network device has its own subfolder, named with an index, with various properties: + // -NetworkCards + // -5 + // -Description = Broadcom 802.11n Network Adapter + // -ServiceName = {E35A72F8-5065-4097-8DFE-C7790774EE4D} + // -8 + // -Description = Marvell Yukon 88E8058 PCI-E Gigabit Ethernet Controller + // -ServiceName = {86226414-5545-4335-A9D1-5BD7120119AD} + // + // The above service name is the name of a subfolder within: + // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces + // + // There may be more subfolders in this interfaces path than listed in the network cards path above: + // -Interfaces + // -{3a539854-6a70-11db-887c-806e6f6e6963} + // -DhcpIPAddress = 0.0.0.0 + // -[more] + // -{E35A72F8-5065-4097-8DFE-C7790774EE4D} + // -DhcpIPAddress = 10.0.1.4 + // -DhcpDefaultGateway = 10.0.1.1 + // -[more] + // -{86226414-5545-4335-A9D1-5BD7120119AD} + // -DhcpIpAddress = 10.0.1.5 + // -DhcpDefaultGateay = 10.0.1.1 + // -[more] + // + // In order to extract this information, we enumerate each network card, and extract the ServiceName value. + // This is then used to open the interface subfolder, and attempt to extract a DhcpDefaultGateway value. + // Once one is found, we're done. + // + // It may be possible to simply enumerate the interface folders until we find one with a DhcpDefaultGateway value. + // However, the technique used is the technique most cited on the web, and we assume it to be more correct. + + if(ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, // Open registry key or predifined key + networkCardsPath, // Name of registry subkey to open + 0, // Reserved - must be zero + KEY_READ, // Mask - desired access rights + &networkCardsKey)) // Pointer to output key + { + // Unable to open network cards keys + return -1; + } + + if(ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, // Open registry key or predefined key + interfacesPath, // Name of registry subkey to open + 0, // Reserved - must be zero + KEY_READ, // Mask - desired access rights + &interfacesKey)) // Pointer to output key + { + // Unable to open interfaces key + RegCloseKey(networkCardsKey); + return -1; + } + + // Figure out how many subfolders are within the NetworkCards folder + RegQueryInfoKey(networkCardsKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + //printf( "Number of subkeys: %u\n", (unsigned int)numSubKeys); + + // Enumrate through each subfolder within the NetworkCards folder + for(i = 0; i < numSubKeys && !done; i++) + { + keyNameLength = MAX_KEY_LENGTH; + if(ERROR_SUCCESS == RegEnumKeyEx(networkCardsKey, // Open registry key + i, // Index of subkey to retrieve + keyName, // Buffer that receives the name of the subkey + &keyNameLength, // Variable that receives the size of the above buffer + NULL, // Reserved - must be NULL + NULL, // Buffer that receives the class string + NULL, // Variable that receives the size of the above buffer + NULL)) // Variable that receives the last write time of subkey + { + if(RegOpenKeyEx(networkCardsKey, keyName, 0, KEY_READ, &networkCardKey) == ERROR_SUCCESS) + { + keyValueLength = MAX_VALUE_LENGTH; + if(ERROR_SUCCESS == RegQueryValueEx(networkCardKey, // Open registry key + STR_SERVICENAME, // Name of key to query + NULL, // Reserved - must be NULL + &keyValueType, // Receives value type + (LPBYTE)keyValue, // Receives value + &keyValueLength)) // Receives value length in bytes + { +// printf("keyValue: %s\n", keyValue); + if(RegOpenKeyEx(interfacesKey, keyValue, 0, KEY_READ, &interfaceKey) == ERROR_SUCCESS) + { + gatewayValueLength = MAX_VALUE_LENGTH; + if(ERROR_SUCCESS == RegQueryValueEx(interfaceKey, // Open registry key + STR_DHCPDEFAULTGATEWAY, // Name of key to query + NULL, // Reserved - must be NULL + &gatewayValueType, // Receives value type + (LPBYTE)gatewayValue, // Receives value + &gatewayValueLength)) // Receives value length in bytes + { + // Check to make sure it's a string + if((gatewayValueType == REG_MULTI_SZ || gatewayValueType == REG_SZ) && (gatewayValueLength > 1)) + { + //printf("gatewayValue: %s\n", gatewayValue); + done = 1; + } + } + else if(ERROR_SUCCESS == RegQueryValueEx(interfaceKey, // Open registry key + STR_DEFAULTGATEWAY, // Name of key to query + NULL, // Reserved - must be NULL + &gatewayValueType, // Receives value type + (LPBYTE)gatewayValue,// Receives value + &gatewayValueLength)) // Receives value length in bytes + { + // Check to make sure it's a string + if((gatewayValueType == REG_MULTI_SZ || gatewayValueType == REG_SZ) && (gatewayValueLength > 1)) + { + //printf("gatewayValue: %s\n", gatewayValue); + done = 1; + } + } + RegCloseKey(interfaceKey); + } + } + RegCloseKey(networkCardKey); + } + } + } + + RegCloseKey(interfacesKey); + RegCloseKey(networkCardsKey); + + if(done) + { +#if UNICODE + char tmp[32]; + for(i = 0; i < 32; i++) { + tmp[i] = (char)gatewayValue[i]; + if(!tmp[i]) + break; + } + tmp[31] = '\0'; + *addr = inet_addr(tmp); +#else + *addr = inet_addr(gatewayValue); +#endif + return 0; + } + + return -1; +} +#endif /* #ifdef USE_WIN32_CODE */ + +#ifdef USE_WIN32_CODE_2 +int getdefaultgateway(in_addr_t *addr) +{ + MIB_IPFORWARDROW ip_forward; + memset(&ip_forward, 0, sizeof(ip_forward)); + if(GetBestRoute(inet_addr("0.0.0.0"), 0, &ip_forward) != NO_ERROR) + return -1; + *addr = ip_forward.dwForwardNextHop; + return 0; +} +#endif /* #ifdef USE_WIN32_CODE_2 */ + +#ifdef USE_HAIKU_CODE +int getdefaultgateway(in_addr_t *addr) +{ + int fd, ret = -1; + struct ifconf config; + void *buffer = NULL; + struct ifreq *interface; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + return -1; + } + if (ioctl(fd, SIOCGRTSIZE, &config, sizeof(config)) != 0) { + goto fail; + } + if (config.ifc_value < 1) { + goto fail; /* No routes */ + } + if ((buffer = malloc(config.ifc_value)) == NULL) { + goto fail; + } + config.ifc_len = config.ifc_value; + config.ifc_buf = buffer; + if (ioctl(fd, SIOCGRTTABLE, &config, sizeof(config)) != 0) { + goto fail; + } + for (interface = buffer; + (uint8_t *)interface < (uint8_t *)buffer + config.ifc_len; ) { + struct route_entry route = interface->ifr_route; + int intfSize; + if (route.flags & (RTF_GATEWAY | RTF_DEFAULT)) { + *addr = ((struct sockaddr_in *)route.gateway)->sin_addr.s_addr; + ret = 0; + break; + } + intfSize = sizeof(route) + IF_NAMESIZE; + if (route.destination != NULL) { + intfSize += route.destination->sa_len; + } + if (route.mask != NULL) { + intfSize += route.mask->sa_len; + } + if (route.gateway != NULL) { + intfSize += route.gateway->sa_len; + } + interface = (struct ifreq *)((uint8_t *)interface + intfSize); + } +fail: + free(buffer); + close(fd); + return ret; +} +#endif /* #ifdef USE_HAIKU_CODE */ + +#if !defined(USE_PROC_NET_ROUTE) && !defined(USE_SOCKET_ROUTE) && !defined(USE_SYSCTL_NET_ROUTE) && !defined(USE_WIN32_CODE) && !defined(USE_WIN32_CODE_2) && !defined(USE_HAIKU_CODE) +int getdefaultgateway(in_addr_t * addr) +{ + return -1; +} +#endif diff --git a/ext/libnatpmp/getgateway.h b/ext/libnatpmp/getgateway.h new file mode 100644 index 0000000..5d3df73 --- /dev/null +++ b/ext/libnatpmp/getgateway.h @@ -0,0 +1,49 @@ +/* $Id: getgateway.h,v 1.8 2014/04/22 09:15:40 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2014, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef __GETGATEWAY_H__ +#define __GETGATEWAY_H__ + +#ifdef WIN32 +#if !defined(_MSC_VER) || _MSC_VER >= 1600 +#include +#else +typedef unsigned long uint32_t; +typedef unsigned short uint16_t; +#endif +#define in_addr_t uint32_t +#endif +/* #include "declspec.h" */ + +/* getdefaultgateway() : + * return value : + * 0 : success + * -1 : failure */ +/* LIBSPEC */int getdefaultgateway(in_addr_t * addr); + +#endif diff --git a/ext/libnatpmp/libnatpmpmodule.c b/ext/libnatpmp/libnatpmpmodule.c new file mode 100644 index 0000000..0fd9914 --- /dev/null +++ b/ext/libnatpmp/libnatpmpmodule.c @@ -0,0 +1,281 @@ +/* $Id: libnatpmpmodule.c,v 1.7 2012/03/05 19:38:37 nanard Exp $ */ +/* libnatpmp + * http://miniupnp.free.fr/libnatpmp.html +Copyright (c) 2007-2011, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#ifdef WIN32 +#include +#else +#include +#include +#endif + +#define STATICLIB +#include "structmember.h" +#include "natpmp.h" + +/* for compatibility with Python < 2.4 */ +#ifndef Py_RETURN_NONE +#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None +#endif + +#ifndef Py_RETURN_TRUE +#define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True +#endif + +#ifndef Py_RETURN_FALSE +#define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False +#endif + +typedef struct { + PyObject_HEAD + + /* Type-specific fields go here. */ + unsigned int discoverdelay; + + natpmp_t natpmp; +} NATPMPObject; + +static PyMemberDef NATPMP_members[] = { + {"discoverdelay", T_UINT, offsetof(NATPMPObject, discoverdelay), + 0/*READWRITE*/, "value in ms used to wait for NATPMP responses" + }, + {NULL} +}; + +static PyObject * +NATPMPObject_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + NATPMPObject *self; + + self = (NATPMPObject *)type->tp_alloc(type, 0); + if (self) { + initnatpmp(&self->natpmp, 0, 0); + } + + return (PyObject *)self; +} + +static void +NATPMPObject_dealloc(NATPMPObject *self) +{ + closenatpmp(&self->natpmp); + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject * +NATPMP_externalipaddress(NATPMPObject *self) +{ + int r; + struct timeval timeout; + fd_set fds; + natpmpresp_t response; + + r = sendpublicaddressrequest(&self->natpmp); + + if (r < 0) { +#ifdef ENABLE_STRNATPMPERR + PyErr_SetString(PyExc_Exception, strnatpmperr(r)); +#endif + return NULL; + } + + do { + FD_ZERO(&fds); + FD_SET(self->natpmp.s, &fds); + getnatpmprequesttimeout(&self->natpmp, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + r = readnatpmpresponseorretry(&self->natpmp, &response); + if (r < 0 && r != NATPMP_TRYAGAIN) { +#ifdef ENABLE_STRNATPMPERR + PyErr_SetString(PyExc_Exception, strnatpmperr(r)); +#endif + return NULL; + } + } while (r == NATPMP_TRYAGAIN); + + return Py_BuildValue("s", inet_ntoa(response.pnu.publicaddress.addr)); +} + +static PyObject * +NATPMP_domapping(natpmp_t *n, unsigned short eport, unsigned short iport, + const char *protocol, unsigned int lifetime) +{ + int proto; + struct timeval timeout; + fd_set fds; + natpmpresp_t response; + int r; + + if (!strncasecmp("tcp", protocol, 3)) { + proto = NATPMP_PROTOCOL_TCP; + } else if (!strncasecmp("udp", protocol, 3)) { + proto = NATPMP_PROTOCOL_UDP; + } else { + PyErr_SetString(PyExc_Exception, "Unknown protocol"); + return NULL; + } + + r = sendnewportmappingrequest(n, proto, iport, eport, + lifetime); + + if (r < 0) { +#ifdef ENABLE_STRNATPMPERR + PyErr_SetString(PyExc_Exception, strnatpmperr(r)); +#endif + return NULL; + } + + do { + FD_ZERO(&fds); + FD_SET(n->s, &fds); + getnatpmprequesttimeout(n, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + r = readnatpmpresponseorretry(n, &response); + if (r < 0 && r != NATPMP_TRYAGAIN) { +#ifdef ENABLE_STRNATPMPERR + PyErr_SetString(PyExc_Exception, strnatpmperr(r)); +#endif + return NULL; + } + } while (r == NATPMP_TRYAGAIN); + + return Py_BuildValue("H", response.pnu.newportmapping.mappedpublicport); +} + + +/* AddPortMapping(externalPort, protocol, internalPort, lifetime) + * protocol is 'UDP' or 'TCP' */ +static PyObject * +NATPMP_addportmapping(NATPMPObject *self, PyObject *args) +{ + unsigned short eport; + unsigned short iport; + unsigned int lifetime; + const char *protocol; + + if (!PyArg_ParseTuple(args, "HsHI", &eport, &protocol, &iport, &lifetime)) + return NULL; + + return NATPMP_domapping(&self->natpmp, eport, iport, protocol, lifetime); +} + +/* DeletePortMapping(externalPort, protocol, internalPort) + * protocol is 'UDP' or 'TCP' */ +static PyObject * +NATPMP_deleteportmapping(NATPMPObject *self, PyObject *args) +{ + unsigned short eport; + unsigned short iport; + const char *protocol; + + if (!PyArg_ParseTuple(args, "HsH", &eport, &protocol, &iport)) + return NULL; + + return NATPMP_domapping(&self->natpmp, eport, iport, protocol, 0); +} + +/* natpmp.NATPMP object Method Table */ +static PyMethodDef NATPMP_methods[] = { + {"externalipaddress", (PyCFunction)NATPMP_externalipaddress, METH_NOARGS, + "return external IP address" + }, + {"addportmapping", (PyCFunction)NATPMP_addportmapping, METH_VARARGS, + "add a port mapping" + }, + {"deleteportmapping", (PyCFunction)NATPMP_deleteportmapping, METH_VARARGS, + "delete a port mapping" + }, + {NULL} /* Sentinel */ +}; + +static PyTypeObject NATPMPType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "libnatpmp.NATPMP", /*tp_name*/ + sizeof(NATPMPObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)NATPMPObject_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "NATPMP objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + NATPMP_methods, /* tp_methods */ + NATPMP_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + NATPMPObject_new, /* tp_new */ +}; + +/* module methods */ +static PyMethodDef libnatpmp_methods[] = { + {NULL} /* Sentinel */ +}; + +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#define PyMODINIT_FUNC void +#endif +PyMODINIT_FUNC +initlibnatpmp(void) +{ + PyObject* m; + + if (PyType_Ready(&NATPMPType) < 0) + return; + + m = Py_InitModule3("libnatpmp", libnatpmp_methods, + "libnatpmp module."); + + Py_INCREF(&NATPMPType); + PyModule_AddObject(m, "NATPMP", (PyObject *)&NATPMPType); +} + diff --git a/ext/libnatpmp/msvc/libnatpmp.sln b/ext/libnatpmp/msvc/libnatpmp.sln new file mode 100644 index 0000000..ac746d4 --- /dev/null +++ b/ext/libnatpmp/msvc/libnatpmp.sln @@ -0,0 +1,29 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libnatpmp", "libnatpmp.vcproj", "{D59B6527-F3DE-4D26-A08D-52F1EE989301}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "natpmpc-static", "natpmpc-static.vcproj", "{A0B49FA9-98AB-4A74-8B4C-8AB7FA36089B}" + ProjectSection(ProjectDependencies) = postProject + {D59B6527-F3DE-4D26-A08D-52F1EE989301} = {D59B6527-F3DE-4D26-A08D-52F1EE989301} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D59B6527-F3DE-4D26-A08D-52F1EE989301}.Debug|Win32.ActiveCfg = Debug|Win32 + {D59B6527-F3DE-4D26-A08D-52F1EE989301}.Debug|Win32.Build.0 = Debug|Win32 + {D59B6527-F3DE-4D26-A08D-52F1EE989301}.Release|Win32.ActiveCfg = Release|Win32 + {D59B6527-F3DE-4D26-A08D-52F1EE989301}.Release|Win32.Build.0 = Release|Win32 + {A0B49FA9-98AB-4A74-8B4C-8AB7FA36089B}.Debug|Win32.ActiveCfg = Debug|Win32 + {A0B49FA9-98AB-4A74-8B4C-8AB7FA36089B}.Debug|Win32.Build.0 = Debug|Win32 + {A0B49FA9-98AB-4A74-8B4C-8AB7FA36089B}.Release|Win32.ActiveCfg = Release|Win32 + {A0B49FA9-98AB-4A74-8B4C-8AB7FA36089B}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ext/libnatpmp/msvc/libnatpmp.vcproj b/ext/libnatpmp/msvc/libnatpmp.vcproj new file mode 100644 index 0000000..9bae5c1 --- /dev/null +++ b/ext/libnatpmp/msvc/libnatpmp.vcproj @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext/libnatpmp/msvc/natpmpc-static.vcproj b/ext/libnatpmp/msvc/natpmpc-static.vcproj new file mode 100644 index 0000000..c2052d9 --- /dev/null +++ b/ext/libnatpmp/msvc/natpmpc-static.vcproj @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext/libnatpmp/natpmp-jni.c b/ext/libnatpmp/natpmp-jni.c new file mode 100644 index 0000000..feec1ce --- /dev/null +++ b/ext/libnatpmp/natpmp-jni.c @@ -0,0 +1,157 @@ +#ifdef __CYGWIN__ +#include +#define __int64 uint64_t +#endif + +#ifdef WIN32 +#include +#include +#include +#endif + +#include +#include "natpmp.h" + +#include "fr_free_miniupnp_libnatpmp_NatPmp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT void JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_init (JNIEnv *env, jobject obj, jint forcegw, jint forcedgw) { + natpmp_t *p = malloc (sizeof(natpmp_t)); + if (p == NULL) return; + + initnatpmp(p, forcegw, (in_addr_t) forcedgw); + + jobject wrapped = (*env)->NewDirectByteBuffer(env, p, sizeof(natpmp_t)); + if (wrapped == NULL) return; + + jclass thisClass = (*env)->GetObjectClass(env,obj); + if (thisClass == NULL) return; + + jfieldID fid = (*env)->GetFieldID(env, thisClass, "natpmp", "Ljava/nio/ByteBuffer;"); + if (fid == NULL) return; + (*env)->SetObjectField(env, obj, fid, wrapped); +} + +JNIEXPORT void JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_free (JNIEnv *env, jobject obj) { + + jclass thisClass = (*env)->GetObjectClass(env,obj); + if (thisClass == NULL) return; + + jfieldID fid = (*env)->GetFieldID(env, thisClass, "natpmp", "Ljava/nio/ByteBuffer;"); + + if (fid == NULL) return; + jobject wrapped = (*env)->GetObjectField(env, obj, fid); + if (wrapped == NULL) return; + + natpmp_t* natpmp = (natpmp_t*) (*env)->GetDirectBufferAddress(env, wrapped); + + closenatpmp(natpmp); + + if (natpmp == NULL) return; + free(natpmp); + + (*env)->SetObjectField(env, obj, fid, NULL); +} + +static natpmp_t* getNatPmp(JNIEnv* env, jobject obj) { + jclass thisClass = (*env)->GetObjectClass(env,obj); + if (thisClass == NULL) return NULL; + + jfieldID fid = (*env)->GetFieldID(env, thisClass, "natpmp", "Ljava/nio/ByteBuffer;"); + + if (fid == NULL) return NULL; + jobject wrapped = (*env)->GetObjectField(env, obj, fid); + if (wrapped == NULL) return NULL; + + natpmp_t* natpmp = (natpmp_t*) (*env)->GetDirectBufferAddress(env, wrapped); + + return natpmp; +} + +JNIEXPORT jint JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_sendPublicAddressRequest(JNIEnv* env, jobject obj) { + natpmp_t* natpmp = getNatPmp(env, obj); + if (natpmp == NULL) return -1; + + return sendpublicaddressrequest(natpmp); +} + + +JNIEXPORT void JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_startup(JNIEnv* env, jclass cls) { + (void)env; + (void)cls; +#ifdef WIN32 + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(2, 2); + WSAStartup(wVersionRequested, &wsaData); +#endif +} + + +JNIEXPORT jint JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_sendNewPortMappingRequest(JNIEnv* env, jobject obj, jint protocol, jint privateport, jint publicport, jint lifetime) { + natpmp_t* natpmp = getNatPmp(env, obj); + if (natpmp == NULL) return -1; + + return sendnewportmappingrequest(natpmp, protocol, privateport, publicport, lifetime); +} + +JNIEXPORT jlong JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_getNatPmpRequestTimeout(JNIEnv* env, jobject obj) { + natpmp_t* natpmp = getNatPmp(env, obj); + + struct timeval timeout; + + getnatpmprequesttimeout(natpmp, &timeout); + + return ((jlong) timeout.tv_sec) * 1000 + (timeout.tv_usec / 1000); + +} + +#define SET_FIELD(prefix, name, type, longtype) { \ + jfieldID fid = (*env)->GetFieldID(env, thisClass, #name, type); \ + if (fid == NULL) return -1; \ + (*env)->Set ## longtype ## Field(env, response, fid, resp. prefix name); \ +} + +JNIEXPORT jint JNICALL Java_fr_free_miniupnp_libnatpmp_NatPmp_readNatPmpResponseOrRetry(JNIEnv* env, jobject obj, jobject response) { + + natpmp_t* natpmp = getNatPmp(env, obj); + natpmpresp_t resp; + int result = readnatpmpresponseorretry(natpmp, &resp); + + if (result != 0) { + return result; + } + + jclass thisClass = (*env)->GetObjectClass(env, response); + if (thisClass == NULL) return -1; + + SET_FIELD(,type, "S", Short); + SET_FIELD(,resultcode, "S", Short); + + jfieldID fid = (*env)->GetFieldID(env, thisClass, "epoch", "J"); + if (fid == NULL) return -1; + (*env)->SetLongField(env, response, fid, ((jlong)resp.epoch) * 1000); + + if (resp.type == 0) { + jfieldID fid = (*env)->GetFieldID(env, thisClass, "addr", "I"); + if (fid == NULL) return -1; + (*env)->SetIntField(env, response, fid, resp.pnu.publicaddress.addr.s_addr); + + + } else { + SET_FIELD(pnu.newportmapping., privateport, "I", Int); + SET_FIELD(pnu.newportmapping., mappedpublicport, "I", Int); + + jfieldID fid = (*env)->GetFieldID(env, thisClass, "lifetime", "J"); + if (fid == NULL) return -1; + (*env)->SetLongField(env, response, fid, ((jlong) resp.pnu.newportmapping.lifetime) * 1000 * 1000); + } + return result; +} + + +#ifdef __cplusplus +} +#endif diff --git a/ext/libnatpmp/natpmp.c b/ext/libnatpmp/natpmp.c new file mode 100644 index 0000000..9843c41 --- /dev/null +++ b/ext/libnatpmp/natpmp.c @@ -0,0 +1,383 @@ +/* $Id: natpmp.c,v 1.20 2015/05/27 12:43:15 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2015, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#ifdef __linux__ +#define _BSD_SOURCE 1 +#endif +#include +#include +#if !defined(_MSC_VER) +#include +#endif +#ifdef WIN32 +#include +#include +#include +#include +#ifndef EWOULDBLOCK +#define EWOULDBLOCK WSAEWOULDBLOCK +#endif +#ifndef ECONNREFUSED +#define ECONNREFUSED WSAECONNREFUSED +#endif +#include "wingettimeofday.h" +#define gettimeofday natpmp_gettimeofday +#else +#include +#include +#include +#include +#include +#define closesocket close +#endif +#include "natpmp.h" +#include "getgateway.h" +#include + +LIBSPEC int initnatpmp(natpmp_t * p, int forcegw, in_addr_t forcedgw) +{ +#ifdef WIN32 + u_long ioctlArg = 1; +#else + int flags; +#endif + struct sockaddr_in addr; + if(!p) + return NATPMP_ERR_INVALIDARGS; + memset(p, 0, sizeof(natpmp_t)); + p->s = socket(PF_INET, SOCK_DGRAM, 0); + if(p->s < 0) + return NATPMP_ERR_SOCKETERROR; +#ifdef WIN32 + if(ioctlsocket(p->s, FIONBIO, &ioctlArg) == SOCKET_ERROR) + return NATPMP_ERR_FCNTLERROR; +#else + if((flags = fcntl(p->s, F_GETFL, 0)) < 0) + return NATPMP_ERR_FCNTLERROR; + if(fcntl(p->s, F_SETFL, flags | O_NONBLOCK) < 0) + return NATPMP_ERR_FCNTLERROR; +#endif + + if(forcegw) { + p->gateway = forcedgw; + } else { + if(getdefaultgateway(&(p->gateway)) < 0) + return NATPMP_ERR_CANNOTGETGATEWAY; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(NATPMP_PORT); + addr.sin_addr.s_addr = p->gateway; + if(connect(p->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) + return NATPMP_ERR_CONNECTERR; + return 0; +} + +LIBSPEC int closenatpmp(natpmp_t * p) +{ + if(!p) + return NATPMP_ERR_INVALIDARGS; + if(closesocket(p->s) < 0) + return NATPMP_ERR_CLOSEERR; + return 0; +} + +int sendpendingrequest(natpmp_t * p) +{ + int r; +/* struct sockaddr_in addr;*/ + if(!p) + return NATPMP_ERR_INVALIDARGS; +/* memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(NATPMP_PORT); + addr.sin_addr.s_addr = p->gateway; + r = (int)sendto(p->s, p->pending_request, p->pending_request_len, 0, + (struct sockaddr *)&addr, sizeof(addr));*/ + r = (int)send(p->s, (const char *)p->pending_request, p->pending_request_len, 0); + return (r<0) ? NATPMP_ERR_SENDERR : r; +} + +int sendnatpmprequest(natpmp_t * p) +{ + int n; + if(!p) + return NATPMP_ERR_INVALIDARGS; + /* TODO : check if no request is already pending */ + p->has_pending_request = 1; + p->try_number = 1; + n = sendpendingrequest(p); + gettimeofday(&p->retry_time, NULL); // check errors ! + p->retry_time.tv_usec += 250000; /* add 250ms */ + if(p->retry_time.tv_usec >= 1000000) { + p->retry_time.tv_usec -= 1000000; + p->retry_time.tv_sec++; + } + return n; +} + +LIBSPEC int getnatpmprequesttimeout(natpmp_t * p, struct timeval * timeout) +{ + struct timeval now; + if(!p || !timeout) + return NATPMP_ERR_INVALIDARGS; + if(!p->has_pending_request) + return NATPMP_ERR_NOPENDINGREQ; + if(gettimeofday(&now, NULL) < 0) + return NATPMP_ERR_GETTIMEOFDAYERR; + timeout->tv_sec = p->retry_time.tv_sec - now.tv_sec; + timeout->tv_usec = p->retry_time.tv_usec - now.tv_usec; + if(timeout->tv_usec < 0) { + timeout->tv_usec += 1000000; + timeout->tv_sec--; + } + return 0; +} + +LIBSPEC int sendpublicaddressrequest(natpmp_t * p) +{ + if(!p) + return NATPMP_ERR_INVALIDARGS; + //static const unsigned char request[] = { 0, 0 }; + p->pending_request[0] = 0; + p->pending_request[1] = 0; + p->pending_request_len = 2; + // TODO: return 0 instead of sizeof(request) ?? + return sendnatpmprequest(p); +} + +LIBSPEC int sendnewportmappingrequest(natpmp_t * p, int protocol, + uint16_t privateport, uint16_t publicport, + uint32_t lifetime) +{ + if(!p || (protocol!=NATPMP_PROTOCOL_TCP && protocol!=NATPMP_PROTOCOL_UDP)) + return NATPMP_ERR_INVALIDARGS; + p->pending_request[0] = 0; + p->pending_request[1] = protocol; + p->pending_request[2] = 0; + p->pending_request[3] = 0; + /* break strict-aliasing rules : + *((uint16_t *)(p->pending_request + 4)) = htons(privateport); */ + p->pending_request[4] = (privateport >> 8) & 0xff; + p->pending_request[5] = privateport & 0xff; + /* break stric-aliasing rules : + *((uint16_t *)(p->pending_request + 6)) = htons(publicport); */ + p->pending_request[6] = (publicport >> 8) & 0xff; + p->pending_request[7] = publicport & 0xff; + /* break stric-aliasing rules : + *((uint32_t *)(p->pending_request + 8)) = htonl(lifetime); */ + p->pending_request[8] = (lifetime >> 24) & 0xff; + p->pending_request[9] = (lifetime >> 16) & 0xff; + p->pending_request[10] = (lifetime >> 8) & 0xff; + p->pending_request[11] = lifetime & 0xff; + p->pending_request_len = 12; + return sendnatpmprequest(p); +} + +LIBSPEC int readnatpmpresponse(natpmp_t * p, natpmpresp_t * response) +{ + unsigned char buf[16]; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + int n; + if(!p) + return NATPMP_ERR_INVALIDARGS; + n = recvfrom(p->s, (char *)buf, sizeof(buf), 0, + (struct sockaddr *)&addr, &addrlen); + if(n<0) +#ifdef WIN32 + switch(WSAGetLastError()) { +#else + switch(errno) { +#endif + /*case EAGAIN:*/ + case EWOULDBLOCK: + n = NATPMP_TRYAGAIN; + break; + case ECONNREFUSED: + n = NATPMP_ERR_NOGATEWAYSUPPORT; + break; + default: + n = NATPMP_ERR_RECVFROM; + } + /* check that addr is correct (= gateway) */ + else if(addr.sin_addr.s_addr != p->gateway) + n = NATPMP_ERR_WRONGPACKETSOURCE; + else { + response->resultcode = ntohs(*((uint16_t *)(buf + 2))); + response->epoch = ntohl(*((uint32_t *)(buf + 4))); + if(buf[0] != 0) + n = NATPMP_ERR_UNSUPPORTEDVERSION; + else if(buf[1] < 128 || buf[1] > 130) + n = NATPMP_ERR_UNSUPPORTEDOPCODE; + else if(response->resultcode != 0) { + switch(response->resultcode) { + case 1: + n = NATPMP_ERR_UNSUPPORTEDVERSION; + break; + case 2: + n = NATPMP_ERR_NOTAUTHORIZED; + break; + case 3: + n = NATPMP_ERR_NETWORKFAILURE; + break; + case 4: + n = NATPMP_ERR_OUTOFRESOURCES; + break; + case 5: + n = NATPMP_ERR_UNSUPPORTEDOPCODE; + break; + default: + n = NATPMP_ERR_UNDEFINEDERROR; + } + } else { + response->type = buf[1] & 0x7f; + if(buf[1] == 128) + //response->publicaddress.addr = *((uint32_t *)(buf + 8)); + response->pnu.publicaddress.addr.s_addr = *((uint32_t *)(buf + 8)); + else { + response->pnu.newportmapping.privateport = ntohs(*((uint16_t *)(buf + 8))); + response->pnu.newportmapping.mappedpublicport = ntohs(*((uint16_t *)(buf + 10))); + response->pnu.newportmapping.lifetime = ntohl(*((uint32_t *)(buf + 12))); + } + n = 0; + } + } + return n; +} + +int readnatpmpresponseorretry(natpmp_t * p, natpmpresp_t * response) +{ + int n; + if(!p || !response) + return NATPMP_ERR_INVALIDARGS; + if(!p->has_pending_request) + return NATPMP_ERR_NOPENDINGREQ; + n = readnatpmpresponse(p, response); + if(n<0) { + if(n==NATPMP_TRYAGAIN) { + struct timeval now; + gettimeofday(&now, NULL); // check errors ! + if(timercmp(&now, &p->retry_time, >=)) { + int delay, r; + if(p->try_number >= 9) { + return NATPMP_ERR_NOGATEWAYSUPPORT; + } + /*printf("retry! %d\n", p->try_number);*/ + delay = 250 * (1<try_number); // ms + /*for(i=0; itry_number; i++) + delay += delay;*/ + p->retry_time.tv_sec += (delay / 1000); + p->retry_time.tv_usec += (delay % 1000) * 1000; + if(p->retry_time.tv_usec >= 1000000) { + p->retry_time.tv_usec -= 1000000; + p->retry_time.tv_sec++; + } + p->try_number++; + r = sendpendingrequest(p); + if(r<0) + return r; + } + } + } else { + p->has_pending_request = 0; + } + return n; +} + +#ifdef ENABLE_STRNATPMPERR +LIBSPEC const char * strnatpmperr(int r) +{ + const char * s; + switch(r) { + case NATPMP_ERR_INVALIDARGS: + s = "invalid arguments"; + break; + case NATPMP_ERR_SOCKETERROR: + s = "socket() failed"; + break; + case NATPMP_ERR_CANNOTGETGATEWAY: + s = "cannot get default gateway ip address"; + break; + case NATPMP_ERR_CLOSEERR: +#ifdef WIN32 + s = "closesocket() failed"; +#else + s = "close() failed"; +#endif + break; + case NATPMP_ERR_RECVFROM: + s = "recvfrom() failed"; + break; + case NATPMP_ERR_NOPENDINGREQ: + s = "no pending request"; + break; + case NATPMP_ERR_NOGATEWAYSUPPORT: + s = "the gateway does not support nat-pmp"; + break; + case NATPMP_ERR_CONNECTERR: + s = "connect() failed"; + break; + case NATPMP_ERR_WRONGPACKETSOURCE: + s = "packet not received from the default gateway"; + break; + case NATPMP_ERR_SENDERR: + s = "send() failed"; + break; + case NATPMP_ERR_FCNTLERROR: + s = "fcntl() failed"; + break; + case NATPMP_ERR_GETTIMEOFDAYERR: + s = "gettimeofday() failed"; + break; + case NATPMP_ERR_UNSUPPORTEDVERSION: + s = "unsupported nat-pmp version error from server"; + break; + case NATPMP_ERR_UNSUPPORTEDOPCODE: + s = "unsupported nat-pmp opcode error from server"; + break; + case NATPMP_ERR_UNDEFINEDERROR: + s = "undefined nat-pmp server error"; + break; + case NATPMP_ERR_NOTAUTHORIZED: + s = "not authorized"; + break; + case NATPMP_ERR_NETWORKFAILURE: + s = "network failure"; + break; + case NATPMP_ERR_OUTOFRESOURCES: + s = "nat-pmp server out of resources"; + break; + default: + s = "Unknown libnatpmp error"; + } + return s; +} +#endif + diff --git a/ext/libnatpmp/natpmp.def b/ext/libnatpmp/natpmp.def new file mode 100644 index 0000000..cd11003 --- /dev/null +++ b/ext/libnatpmp/natpmp.def @@ -0,0 +1,11 @@ +LIBRARY +; libnatpmp library + +EXPORTS + initnatpmp + closenatpmp + sendpublicaddressrequest + sendnewportmappingrequest + getnatpmprequesttimeout + readnatpmpresponseorretry + strnatpmperr diff --git a/ext/libnatpmp/natpmp.h b/ext/libnatpmp/natpmp.h new file mode 100644 index 0000000..7889d20 --- /dev/null +++ b/ext/libnatpmp/natpmp.h @@ -0,0 +1,219 @@ +/* $Id: natpmp.h,v 1.20 2014/04/22 09:15:40 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2014, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef __NATPMP_H__ +#define __NATPMP_H__ + +/* NAT-PMP Port as defined by the NAT-PMP draft */ +#define NATPMP_PORT (5351) + +#include +#if !defined(_MSC_VER) +#include +#endif /* !defined(_MSC_VER) */ + +#ifdef WIN32 +#include +#if !defined(_MSC_VER) || _MSC_VER >= 1600 +#include +#else /* !defined(_MSC_VER) || _MSC_VER >= 1600 */ +typedef unsigned long uint32_t; +typedef unsigned short uint16_t; +#endif /* !defined(_MSC_VER) || _MSC_VER >= 1600 */ +#define in_addr_t uint32_t +#include "declspec.h" +#else /* WIN32 */ +#define LIBSPEC +#include +#endif /* WIN32 */ + +/* causes problem when installing. Maybe should it be inlined ? */ +/* #include "declspec.h" */ + +typedef struct { + int s; /* socket */ + in_addr_t gateway; /* default gateway (IPv4) */ + int has_pending_request; + unsigned char pending_request[12]; + int pending_request_len; + int try_number; + struct timeval retry_time; +} natpmp_t; + +typedef struct { + uint16_t type; /* NATPMP_RESPTYPE_* */ + uint16_t resultcode; /* NAT-PMP response code */ + uint32_t epoch; /* Seconds since start of epoch */ + union { + struct { + //in_addr_t addr; + struct in_addr addr; + } publicaddress; + struct { + uint16_t privateport; + uint16_t mappedpublicport; + uint32_t lifetime; + } newportmapping; + } pnu; +} natpmpresp_t; + +/* possible values for type field of natpmpresp_t */ +#define NATPMP_RESPTYPE_PUBLICADDRESS (0) +#define NATPMP_RESPTYPE_UDPPORTMAPPING (1) +#define NATPMP_RESPTYPE_TCPPORTMAPPING (2) + +/* Values to pass to sendnewportmappingrequest() */ +#define NATPMP_PROTOCOL_UDP (1) +#define NATPMP_PROTOCOL_TCP (2) + +/* return values */ +/* NATPMP_ERR_INVALIDARGS : invalid arguments passed to the function */ +#define NATPMP_ERR_INVALIDARGS (-1) +/* NATPMP_ERR_SOCKETERROR : socket() failed. check errno for details */ +#define NATPMP_ERR_SOCKETERROR (-2) +/* NATPMP_ERR_CANNOTGETGATEWAY : can't get default gateway IP */ +#define NATPMP_ERR_CANNOTGETGATEWAY (-3) +/* NATPMP_ERR_CLOSEERR : close() failed. check errno for details */ +#define NATPMP_ERR_CLOSEERR (-4) +/* NATPMP_ERR_RECVFROM : recvfrom() failed. check errno for details */ +#define NATPMP_ERR_RECVFROM (-5) +/* NATPMP_ERR_NOPENDINGREQ : readnatpmpresponseorretry() called while + * no NAT-PMP request was pending */ +#define NATPMP_ERR_NOPENDINGREQ (-6) +/* NATPMP_ERR_NOGATEWAYSUPPORT : the gateway does not support NAT-PMP */ +#define NATPMP_ERR_NOGATEWAYSUPPORT (-7) +/* NATPMP_ERR_CONNECTERR : connect() failed. check errno for details */ +#define NATPMP_ERR_CONNECTERR (-8) +/* NATPMP_ERR_WRONGPACKETSOURCE : packet not received from the network gateway */ +#define NATPMP_ERR_WRONGPACKETSOURCE (-9) +/* NATPMP_ERR_SENDERR : send() failed. check errno for details */ +#define NATPMP_ERR_SENDERR (-10) +/* NATPMP_ERR_FCNTLERROR : fcntl() failed. check errno for details */ +#define NATPMP_ERR_FCNTLERROR (-11) +/* NATPMP_ERR_GETTIMEOFDAYERR : gettimeofday() failed. check errno for details */ +#define NATPMP_ERR_GETTIMEOFDAYERR (-12) + +/* */ +#define NATPMP_ERR_UNSUPPORTEDVERSION (-14) +#define NATPMP_ERR_UNSUPPORTEDOPCODE (-15) + +/* Errors from the server : */ +#define NATPMP_ERR_UNDEFINEDERROR (-49) +#define NATPMP_ERR_NOTAUTHORIZED (-51) +#define NATPMP_ERR_NETWORKFAILURE (-52) +#define NATPMP_ERR_OUTOFRESOURCES (-53) + +/* NATPMP_TRYAGAIN : no data available for the moment. try again later */ +#define NATPMP_TRYAGAIN (-100) + +#ifdef __cplusplus +extern "C" { +#endif + +/* initnatpmp() + * initialize a natpmp_t object + * With forcegw=1 the gateway is not detected automaticaly. + * Return values : + * 0 = OK + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_SOCKETERROR + * NATPMP_ERR_FCNTLERROR + * NATPMP_ERR_CANNOTGETGATEWAY + * NATPMP_ERR_CONNECTERR */ +LIBSPEC int initnatpmp(natpmp_t * p, int forcegw, in_addr_t forcedgw); + +/* closenatpmp() + * close resources associated with a natpmp_t object + * Return values : + * 0 = OK + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_CLOSEERR */ +LIBSPEC int closenatpmp(natpmp_t * p); + +/* sendpublicaddressrequest() + * send a public address NAT-PMP request to the network gateway + * Return values : + * 2 = OK (size of the request) + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_SENDERR */ +LIBSPEC int sendpublicaddressrequest(natpmp_t * p); + +/* sendnewportmappingrequest() + * send a new port mapping NAT-PMP request to the network gateway + * Arguments : + * protocol is either NATPMP_PROTOCOL_TCP or NATPMP_PROTOCOL_UDP, + * lifetime is in seconds. + * To remove a port mapping, set lifetime to zero. + * To remove all port mappings to the host, set lifetime and both ports + * to zero. + * Return values : + * 12 = OK (size of the request) + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_SENDERR */ +LIBSPEC int sendnewportmappingrequest(natpmp_t * p, int protocol, + uint16_t privateport, uint16_t publicport, + uint32_t lifetime); + +/* getnatpmprequesttimeout() + * fills the timeval structure with the timeout duration of the + * currently pending NAT-PMP request. + * Return values : + * 0 = OK + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_GETTIMEOFDAYERR + * NATPMP_ERR_NOPENDINGREQ */ +LIBSPEC int getnatpmprequesttimeout(natpmp_t * p, struct timeval * timeout); + +/* readnatpmpresponseorretry() + * fills the natpmpresp_t structure if possible + * Return values : + * 0 = OK + * NATPMP_TRYAGAIN + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_NOPENDINGREQ + * NATPMP_ERR_NOGATEWAYSUPPORT + * NATPMP_ERR_RECVFROM + * NATPMP_ERR_WRONGPACKETSOURCE + * NATPMP_ERR_UNSUPPORTEDVERSION + * NATPMP_ERR_UNSUPPORTEDOPCODE + * NATPMP_ERR_NOTAUTHORIZED + * NATPMP_ERR_NETWORKFAILURE + * NATPMP_ERR_OUTOFRESOURCES + * NATPMP_ERR_UNSUPPORTEDOPCODE + * NATPMP_ERR_UNDEFINEDERROR */ +LIBSPEC int readnatpmpresponseorretry(natpmp_t * p, natpmpresp_t * response); + +#ifdef ENABLE_STRNATPMPERR +LIBSPEC const char * strnatpmperr(int t); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/libnatpmp/natpmpc.1 b/ext/libnatpmp/natpmpc.1 new file mode 100644 index 0000000..5f0003d --- /dev/null +++ b/ext/libnatpmp/natpmpc.1 @@ -0,0 +1,19 @@ +.TH natpmpc 1 + +.SH NAME +natpmpc \- NAT\-PMP library test client and mapping setter. + +.SH "SYNOPSIS" +Display the public IP address: +.br +\fBnatpmpc\fP + +Add a port mapping: +.br +\fBnatpmpc\fP \-a [lifetime] + +.SH DESCRIPTION + +In order to remove a mapping, set it with a lifetime of 0 seconds. +To remove all mappings for your machine, use 0 as private port and +lifetime. diff --git a/ext/libnatpmp/natpmpc.c b/ext/libnatpmp/natpmpc.c new file mode 100644 index 0000000..611bd2d --- /dev/null +++ b/ext/libnatpmp/natpmpc.c @@ -0,0 +1,244 @@ +/* $Id: natpmpc.c,v 1.13 2012/08/21 17:23:38 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2011, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#if defined(_MSC_VER) +#if _MSC_VER >= 1400 +#define strcasecmp _stricmp +#else +#define strcasecmp stricmp +#endif +#else +#include +#endif +#ifdef WIN32 +#include +#else +#include +#include +#endif +#include "natpmp.h" + +void usage(FILE * out, const char * argv0) +{ + fprintf(out, "Usage :\n"); + fprintf(out, " %s [options]\n", argv0); + fprintf(out, "\tdisplay the public IP address.\n"); + fprintf(out, " %s -h\n", argv0); + fprintf(out, "\tdisplay this help screen.\n"); + fprintf(out, " %s [options] -a [lifetime]\n", argv0); + fprintf(out, "\tadd a port mapping.\n"); + fprintf(out, "\nOption available :\n"); + fprintf(out, " -g ipv4address\n"); + fprintf(out, "\tforce the gateway to be used as destination for NAT-PMP commands.\n"); + fprintf(out, "\n In order to remove a mapping, set it with a lifetime of 0 seconds.\n"); + fprintf(out, " To remove all mappings for your machine, use 0 as private port and lifetime.\n"); +} + +/* sample code for using libnatpmp */ +int main(int argc, char * * argv) +{ + natpmp_t natpmp; + natpmpresp_t response; + int r; + int sav_errno; + struct timeval timeout; + fd_set fds; + int i; + int protocol = 0; + uint16_t privateport = 0; + uint16_t publicport = 0; + uint32_t lifetime = 3600; + int command = 0; + int forcegw = 0; + in_addr_t gateway = 0; + struct in_addr gateway_in_use; + +#ifdef WIN32 + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2,2), &wsaData); + if(nResult != NO_ERROR) + { + fprintf(stderr, "WSAStartup() failed.\n"); + return -1; + } +#endif + + /* argument parsing */ + for(i=1; i i + 1) { + if(1 != sscanf(argv[i+1], "%u", &lifetime)) { + fprintf(stderr, "%s is not a correct 32bits unsigned integer\n", argv[i]); + } else { + i++; + } + } + break; + default: + fprintf(stderr, "Unknown option %s\n", argv[i]); + usage(stderr, argv[0]); + return 1; + } + } else { + fprintf(stderr, "Unknown option %s\n", argv[i]); + usage(stderr, argv[0]); + return 1; + } + } + + /* initnatpmp() */ + r = initnatpmp(&natpmp, forcegw, gateway); + printf("initnatpmp() returned %d (%s)\n", r, r?"FAILED":"SUCCESS"); + if(r<0) + return 1; + + gateway_in_use.s_addr = natpmp.gateway; + printf("using gateway : %s\n", inet_ntoa(gateway_in_use)); + + /* sendpublicaddressrequest() */ + r = sendpublicaddressrequest(&natpmp); + printf("sendpublicaddressrequest returned %d (%s)\n", + r, r==2?"SUCCESS":"FAILED"); + if(r<0) + return 1; + + do { + FD_ZERO(&fds); + FD_SET(natpmp.s, &fds); + getnatpmprequesttimeout(&natpmp, &timeout); + r = select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + if(r<0) { + fprintf(stderr, "select()"); + return 1; + } + r = readnatpmpresponseorretry(&natpmp, &response); + sav_errno = errno; + printf("readnatpmpresponseorretry returned %d (%s)\n", + r, r==0?"OK":(r==NATPMP_TRYAGAIN?"TRY AGAIN":"FAILED")); + if(r<0 && r!=NATPMP_TRYAGAIN) { +#ifdef ENABLE_STRNATPMPERR + fprintf(stderr, "readnatpmpresponseorretry() failed : %s\n", + strnatpmperr(r)); +#endif + fprintf(stderr, " errno=%d '%s'\n", + sav_errno, strerror(sav_errno)); + } + } while(r==NATPMP_TRYAGAIN); + if(r<0) + return 1; + + /* TODO : check that response.type == 0 */ + printf("Public IP address : %s\n", inet_ntoa(response.pnu.publicaddress.addr)); + printf("epoch = %u\n", response.epoch); + + if(command == 'a') { + /* sendnewportmappingrequest() */ + r = sendnewportmappingrequest(&natpmp, protocol, + privateport, publicport, + lifetime); + printf("sendnewportmappingrequest returned %d (%s)\n", + r, r==12?"SUCCESS":"FAILED"); + if(r < 0) + return 1; + + do { + FD_ZERO(&fds); + FD_SET(natpmp.s, &fds); + getnatpmprequesttimeout(&natpmp, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + r = readnatpmpresponseorretry(&natpmp, &response); + printf("readnatpmpresponseorretry returned %d (%s)\n", + r, r==0?"OK":(r==NATPMP_TRYAGAIN?"TRY AGAIN":"FAILED")); + } while(r==NATPMP_TRYAGAIN); + if(r<0) { +#ifdef ENABLE_STRNATPMPERR + fprintf(stderr, "readnatpmpresponseorretry() failed : %s\n", + strnatpmperr(r)); +#endif + return 1; + } + + printf("Mapped public port %hu protocol %s to local port %hu " + "liftime %u\n", + response.pnu.newportmapping.mappedpublicport, + response.type == NATPMP_RESPTYPE_UDPPORTMAPPING ? "UDP" : + (response.type == NATPMP_RESPTYPE_TCPPORTMAPPING ? "TCP" : + "UNKNOWN"), + response.pnu.newportmapping.privateport, + response.pnu.newportmapping.lifetime); + printf("epoch = %u\n", response.epoch); + } + + r = closenatpmp(&natpmp); + printf("closenatpmp() returned %d (%s)\n", r, r==0?"SUCCESS":"FAILED"); + if(r<0) + return 1; + + return 0; +} + diff --git a/ext/libnatpmp/setup.py b/ext/libnatpmp/setup.py new file mode 100644 index 0000000..aa774ee --- /dev/null +++ b/ext/libnatpmp/setup.py @@ -0,0 +1,18 @@ +#! /usr/bin/python +# $Id: setup.py,v 1.3 2012/03/05 04:54:01 nanard Exp $ +# +# python script to build the libnatpmp module under unix +# +# replace libnatpmp.a by libnatpmp.so for shared library usage +from distutils.core import setup, Extension +from distutils import sysconfig +sysconfig.get_config_vars()["OPT"] = '' +sysconfig.get_config_vars()["CFLAGS"] = '' +setup(name="libnatpmp", version="1.0", + ext_modules=[ + Extension(name="libnatpmp", sources=["libnatpmpmodule.c"], + extra_objects=["libnatpmp.a"], + define_macros=[('ENABLE_STRNATPMPERR', None)] + )] + ) + diff --git a/ext/libnatpmp/setupmingw32.py b/ext/libnatpmp/setupmingw32.py new file mode 100644 index 0000000..d02fdfc --- /dev/null +++ b/ext/libnatpmp/setupmingw32.py @@ -0,0 +1,17 @@ +#! /usr/bin/python +# $Id: setupmingw32.py,v 1.3 2012/03/05 04:54:01 nanard Exp $ +# python script to build the miniupnpc module under windows +# +from distutils.core import setup, Extension +from distutils import sysconfig +sysconfig.get_config_vars()["OPT"] = '' +sysconfig.get_config_vars()["CFLAGS"] = '' +setup(name="libnatpmp", version="1.0", + ext_modules=[ + Extension(name="libnatpmp", sources=["libnatpmpmodule.c"], + libraries=["ws2_32"], + extra_objects=["libnatpmp.a"], + define_macros=[('ENABLE_STRNATPMPERR', None)] + )] + ) + diff --git a/ext/libnatpmp/testgetgateway.c b/ext/libnatpmp/testgetgateway.c new file mode 100644 index 0000000..24cbe7d --- /dev/null +++ b/ext/libnatpmp/testgetgateway.c @@ -0,0 +1,57 @@ +/* $Id: testgetgateway.c,v 1.7 2012/08/21 17:13:31 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2011, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#ifdef WIN32 +#include +#else +#include +#include +#endif +#include "getgateway.h" + +int main(int argc, char * * argv) +{ + (void)argc; + (void)argv; + struct in_addr gatewayaddr; + int r; +#ifdef WIN32 + uint32_t temp = 0; + r = getdefaultgateway(&temp); + gatewayaddr.S_un.S_addr = temp; +#else + r = getdefaultgateway(&(gatewayaddr.s_addr)); +#endif + if(r>=0) + printf("default gateway : %s\n", inet_ntoa(gatewayaddr)); + else + fprintf(stderr, "getdefaultgateway() failed\n"); + return 0; +} + diff --git a/ext/libnatpmp/wingettimeofday.c b/ext/libnatpmp/wingettimeofday.c new file mode 100644 index 0000000..cb730e1 --- /dev/null +++ b/ext/libnatpmp/wingettimeofday.c @@ -0,0 +1,60 @@ +/* $Id: wingettimeofday.c,v 1.6 2013/09/10 20:13:26 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2013, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#ifdef WIN32 +#if defined(_MSC_VER) +struct timeval { + long tv_sec; + long tv_usec; +}; +#else +#include +#endif + +typedef struct _FILETIME { + unsigned long dwLowDateTime; + unsigned long dwHighDateTime; +} FILETIME; + +void __stdcall GetSystemTimeAsFileTime(FILETIME*); + +int natpmp_gettimeofday(struct timeval* p, void* tz /* IGNORED */) { + union { + long long ns100; /*time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } _now; + + if(!p) + return -1; + GetSystemTimeAsFileTime( &(_now.ft) ); + p->tv_usec =(long)((_now.ns100 / 10LL) % 1000000LL ); + p->tv_sec = (long)((_now.ns100-(116444736000000000LL))/10000000LL); + return 0; +} +#endif + diff --git a/ext/libnatpmp/wingettimeofday.h b/ext/libnatpmp/wingettimeofday.h new file mode 100644 index 0000000..1d18d9f --- /dev/null +++ b/ext/libnatpmp/wingettimeofday.h @@ -0,0 +1,39 @@ +/* $Id: wingettimeofday.h,v 1.5 2013/09/11 07:22:25 nanard Exp $ */ +/* libnatpmp +Copyright (c) 2007-2013, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef __WINGETTIMEOFDAY_H__ +#define __WINGETTIMEOFDAY_H__ +#ifdef WIN32 +#if defined(_MSC_VER) +#include +#else +#include +#endif +int natpmp_gettimeofday(struct timeval* p, void* tz /* IGNORED */); +#endif +#endif diff --git a/ext/miniupnpc/Changelog.txt b/ext/miniupnpc/Changelog.txt new file mode 100644 index 0000000..078bebc --- /dev/null +++ b/ext/miniupnpc/Changelog.txt @@ -0,0 +1,677 @@ +$Id: Changelog.txt,v 1.223 2016/04/19 21:06:20 nanard Exp $ +miniUPnP client Changelog. + +VERSION 2.0 : released 2016/04/19 + +2016/01/24: + change miniwget to return HTTP status code + increments API_VERSION to 16 + +2016/01/22: + Improve UPNPIGD_IsConnected() to check if WAN address is not private. + parse HTTP response status line in miniwget.c + +2015/10/26: + snprintf() overflow check. check overflow in simpleUPnPcommand2() + +2015/10/25: + fix compilation with old macs + fix compilation with mingw32 (for Appveyor) + fix python module for python <= 2.3 + +2015/10/08: + Change sameport to localport + see https://github.com/miniupnp/miniupnp/pull/120 + increments API_VERSION to 15 + +2015/09/15: + Fix buffer overflow in igd_desc_parse.c/IGDstartelt() + Discovered by Aleksandar Nikolic of Cisco Talos + +2015/08/28: + move ssdpDiscoverDevices() to minissdpc.c + +2015/08/27: + avoid unix socket leak in getDevicesFromMiniSSDPD() + +2015/08/16: + Also accept "Up" as ConnectionStatus value + +2015/07/23: + split getDevicesFromMiniSSDPD + add ttl argument to upnpDiscover() functions + increments API_VERSION to 14 + +2015/07/22: + Read USN from SSDP messages. + +2015/07/15: + Check malloc/calloc + +2015/06/16: + update getDevicesFromMiniSSDPD() to process longer minissdpd + responses + +2015/05/22: + add searchalltypes param to upnpDiscoverDevices() + increments API_VERSION to 13 + +2015/04/30: + upnpc: output version on the terminal + +2015/04/27: + _BSD_SOURCE is deprecated in favor of _DEFAULT_SOURCE + fix CMakeLists.txt COMPILE_DEFINITIONS + fix getDevicesFromMiniSSDPD() not setting scope_id + improve -r command of upnpc command line tool + +2014/11/17: + search all : + upnpDiscoverDevices() / upnpDiscoverAll() functions + listdevices executable + increment API_VERSION to 12 + validate igd_desc_parse + +2014/11/13: + increment API_VERSION to 11 + +2014/11/05: + simplified function GetUPNPUrls() + +2014/09/11: + use remoteHost arg of DeletePortMapping + +2014/09/06: + Fix python3 build + +2014/07/01: + Fix parsing of IGD2 root descriptions + +2014/06/10: + rename LIBSPEC to MINIUPNP_LIBSPEC + +2014/05/15: + Add support for IGD2 AddAnyPortMapping and DeletePortMappingRange + +2014/02/05: + handle EINPROGRESS after connect() + +2014/02/03: + minixml now handle XML comments + +VERSION 1.9 : released 2014/01/31 + +2014/01/31: + added argument remoteHost to UPNP_GetSpecificPortMappingEntry() + increment API_VERSION to 10 + +2013/12/09: + --help and -h arguments in upnpc.c + +2013/10/07: + fixed potential buffer overrun in miniwget.c + Modified UPNP_GetValidIGD() to check for ExternalIpAddress + +2013/08/01: + define MAXHOSTNAMELEN if not already done + +2013/06/06: + update upnpreplyparse to allow larger values (128 chars instead of 64) + +2013/05/14: + Update upnpreplyparse to take into account "empty" elements + validate upnpreplyparse.c code with "make check" + +2013/05/03: + Fix Solaris build thanks to Maciej Małecki + +2013/04/27: + Fix testminiwget.sh for BSD + +2013/03/23: + Fixed Makefile for *BSD + +2013/03/11: + Update Makefile to use JNAerator version 0.11 + +2013/02/11: + Fix testminiwget.sh for use with dash + Use $(DESTDIR) in Makefile + +VERSION 1.8 : released 2013/02/06 + +2012/10/16: + fix testminiwget with no IPv6 support + +2012/09/27: + Rename all include guards to not clash with C99 + (7.1.3 Reserved identifiers). + +2012/08/30: + Added -e option to upnpc program (set description for port mappings) + +2012/08/29: + Python 3 support (thanks to Christopher Foo) + +2012/08/11: + Fix a memory link in UPNP_GetValidIGD() + Try to handle scope id in link local IPv6 URL under MS Windows + +2012/07/20: + Disable HAS_IP_MREQN on DragonFly BSD + +2012/06/28: + GetUPNPUrls() now inserts scope into link-local IPv6 addresses + +2012/06/23: + More error return checks in upnpc.c + #define MINIUPNPC_GET_SRC_ADDR enables receivedata() to get scope_id + parseURL() now parses IPv6 addresses scope + new parameter for miniwget() : IPv6 address scope + increment API_VERSION to 9 + +2012/06/20: + fixed CMakeLists.txt + +2012/05/29 + Improvements in testminiwget.sh + +VERSION 1.7 : released 2012/05/24 + +2012/05/01: + Cleanup settings of CFLAGS in Makefile + Fix signed/unsigned integer comparaisons + +2012/04/20: + Allow to specify protocol with TCP or UDP for -A option + +2012/04/09: + Only try to fetch XML description once in UPNP_GetValidIGD() + Added -ansi flag to compilation, and fixed C++ comments to ANSI C comments. + +2012/04/05: + minor improvements to minihttptestserver.c + +2012/03/15: + upnperrors.c returns valid error string for unrecognized error codes + +2012/03/08: + make minihttptestserver listen on loopback interface instead of 0.0.0.0 + +2012/01/25: + Maven installation thanks to Alexey Kuznetsov + +2012/01/21: + Replace WIN32 macro by _WIN32 + +2012/01/19: + Fixes in java wrappers thanks to Alexey Kuznetsov : + https://github.com/axet/miniupnp/tree/fix-javatest/miniupnpc + Make and install .deb packages (python) thanks to Alexey Kuznetsov : + https://github.com/axet/miniupnp/tree/feature-debbuild/miniupnpc + +2012/01/07: + The multicast interface can now be specified by name with IPv4. + +2012/01/02: + Install man page + +2011/11/25: + added header to Port Mappings list in upnpc.c + +2011/10/09: + Makefile : make clean now removes jnaerator generated files. + MINIUPNPC_VERSION in miniupnpc.h (updated by make) + +2011/09/12: + added rootdescURL to UPNPUrls structure. + +VERSION 1.6 : released 2011/07/25 + +2011/07/25: + Update doc for version 1.6 release + +2011/06/18: + Fix for windows in miniwget.c + +2011/06/04: + display remote host in port mapping listing + +2011/06/03: + Fix in make install : there were missing headers + +2011/05/26: + Fix the socket leak in miniwget thanks to Richard Marsh. + Permit to add leaseduration in -a command. Display lease duration. + +2011/05/15: + Try both LinkLocal and SiteLocal multicast address for SSDP in IPv6 + +2011/05/09: + add a test in testminiwget.sh. + more error checking in miniwget.c + +2011/05/06: + Adding some tool to test and validate miniwget.c + simplified and debugged miniwget.c + +2011/04/11: + moving ReceiveData() to a receivedata.c file. + parsing presentation url + adding IGD v2 WANIPv6FirewallControl commands + +2011/04/10: + update of miniupnpcmodule.c + comments in miniwget.c, update in testminiwget + Adding errors codes from IGD v2 + new functions in upnpc.c for IGD v2 + +2011/04/09: + Support for litteral ip v6 address in miniwget + +2011/04/08: + Adding support for urn:schemas-upnp-org:service:WANIPv6FirewallControl:1 + Updating APIVERSION + Supporting IPV6 in upnpDiscover() + Adding a -6 option to upnpc command line tool + +2011/03/18: + miniwget/parseURL() : return an error when url param is null. + fixing GetListOfPortMappings() + +2011/03/14: + upnpDiscover() now reporting an error code. + improvements in comments. + +2011/03/11: + adding miniupnpcstrings.h.cmake and CMakeLists.txt files. + +2011/02/15: + Implementation of GetListOfPortMappings() + +2011/02/07: + updates to minixml to support character data starting with spaces + minixml now support CDATA + upnpreplyparse treats specificaly + change in simpleUPnPcommand to return the buffer (simplification) + +2011/02/06: + Added leaseDuration argument to AddPortMapping() + Starting to implement GetListOfPortMappings() + +2011/01/11: + updating wingenminiupnpcstrings.c + +2011/01/04: + improving updateminiupnpcstrings.sh + +VERSION 1.5 : released 2011/01/01 + +2010/12/21: + use NO_GETADDRINFO macro to disable the use of getaddrinfo/freeaddrinfo + +2010/12/11: + Improvements on getHTTPResponse() code. + +2010/12/09: + new code for miniwget that handle Chunked transfer encoding + using getHTTPResponse() in SOAP call code + Adding MANIFEST.in for 'python setup.py bdist_rpm' + +2010/11/25: + changes to minissdpc.c to compile under Win32. + see http://miniupnp.tuxfamily.org/forum/viewtopic.php?t=729 + +2010/09/17: + Various improvement to Makefile from Michał Górny + +2010/08/05: + Adding the script "external-ip.sh" from Reuben Hawkins + +2010/06/09: + update to python module to match modification made on 2010/04/05 + update to Java test code to match modification made on 2010/04/05 + all UPNP_* function now return an error if the SOAP request failed + at HTTP level. + +2010/04/17: + Using GetBestRoute() under win32 in order to find the + right interface to use. + +2010/04/12: + Retrying with HTTP/1.1 if HTTP/1.0 failed. see + http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=1703 + +2010/04/07: + avoid returning duplicates in upnpDiscover() + +2010/04/05: + Create a connecthostport.h/.c with connecthostport() function + and use it in miniwget and miniupnpc. + Use getnameinfo() instead of inet_ntop or inet_ntoa + Work to make miniupnpc IPV6 compatible... + Add java test code. + Big changes in order to support device having both WANIPConnection + and WANPPPConnection. + +2010/04/04: + Use getaddrinfo() instead of gethostbyname() in miniwget. + +2010/01/06: + #define _DARWIN_C_SOURCE for Mac OS X + +2009/12/19: + Improve MinGW32 build + +2009/12/11: + adding a MSVC9 project to build the static library and executable + +2009/12/10: + Fixing some compilation stuff for Windows/MinGW + +2009/12/07: + adaptations in Makefile and updateminiupnpcstring.sh for AmigaOS + some fixes for Windows when using virtual ethernet adapters (it is the + case with VMWare installed). + +2009/12/04: + some fixes for AmigaOS compilation + Changed HTTP version to HTTP/1.0 for Soap too (to prevent chunked + transfer encoding) + +2009/12/03: + updating printIDG and testigddescparse.c for debug. + modifications to compile under AmigaOS + adding a testminiwget program + Changed miniwget to advertise itself as HTTP/1.0 to prevent chunked + transfer encoding + +2009/11/26: + fixing updateminiupnpcstrings.sh to take into account + which command that does not return an error code. + +VERSION 1.4 : released 2009/10/30 + +2009/10/16: + using Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS in python module. + +2009/10/10: + Some fixes for compilation under Solaris + compilation fixes : http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=1464 + +2009/09/21: + fixing the code to ignore EINTR during connect() calls. + +2009/08/07: + Set socket timeout for connect() + Some cleanup in miniwget.c + +2009/08/04: + remove multiple redirections with -d in upnpc.c + Print textual error code in upnpc.c + Ignore EINTR during the connect() and poll() calls. + +2009/07/29: + fix in updateminiupnpcstrings.sh if OS name contains "/" + Sending a correct value for MX: field in SSDP request + +2009/07/20: + Change the Makefile to compile under Mac OS X + Fixed a stackoverflow in getDevicesFromMiniSSDPD() + +2009/07/09: + Compile under Haiku + generate miniupnpcstrings.h.in from miniupnpcstrings.h + +2009/06/04: + patching to compile under CygWin and cross compile for minGW + +VERSION 1.3 : + +2009/04/17: + updating python module + Use strtoull() when using C99 + +2009/02/28: + Fixed miniwget.c for compiling under sun + +2008/12/18: + cleanup in Makefile (thanks to Paul de Weerd) + minissdpc.c : win32 compatibility + miniupnpc.c : changed xmlns prefix from 'm' to 'u' + Removed NDEBUG (using DEBUG) + +2008/10/14: + Added the ExternalHost argument to DeletePortMapping() + +2008/10/11: + Added the ExternalHost argument to AddPortMapping() + Put a correct User-Agent: header in HTTP requests. + +VERSION 1.2 : + +2008/10/07: + Update docs + +2008/09/25: + Integrated sameport patch from Dario Meloni : Added a "sameport" + argument to upnpDiscover(). + +2008/07/18: + small modif to make Clang happy :) + +2008/07/17: + #define SOAPPREFIX "s" in miniupnpc.c in order to remove SOAP-ENV... + +2008/07/14: + include declspec.h in installation (to /usr/include/miniupnpc) + +VERSION 1.1 : + +2008/07/04: + standard options for install/ln instead of gnu-specific stuff. + +2008/07/03: + now builds a .dll and .lib with win32. (mingw32) + +2008/04/28: + make install now install the binary of the upnpc tool + +2008/04/27: + added testupnpigd.py + added error strings for miniupnpc "internal" errors + improved python module error/exception reporting. + +2008/04/23: + Completely rewrite igd_desc_parse.c in order to be compatible with + Linksys WAG200G + Added testigddescparse + updated python module + +VERSION 1.0 : + +2008/02/21: + put some #ifdef DEBUG around DisplayNameValueList() + +2008/02/18: + Improved error reporting in upnpcommands.c + UPNP_GetStatusInfo() returns LastConnectionError + +2008/02/16: + better error handling in minisoap.c + improving display of "valid IGD found" in upnpc.c + +2008/02/03: + Fixing UPNP_GetValidIGD() + improved make install :) + +2007/12/22: + Adding upnperrors.c/h to provide a strupnperror() function + used to translate UPnP error codes to string. + +2007/12/19: + Fixing getDevicesFromMiniSSDPD() + improved error reporting of UPnP functions + +2007/12/18: + It is now possible to specify a different location for MiniSSDPd socket. + working with MiniSSDPd is now more efficient. + python module improved. + +2007/12/16: + improving error reporting + +2007/12/13: + Try to improve compatibility by using HTTP/1.0 instead of 1.1 and + XML a bit different for SOAP. + +2007/11/25: + fixed select() call for linux + +2007/11/15: + Added -fPIC to CFLAG for better shared library code. + +2007/11/02: + Fixed a potential socket leak in miniwget2() + +2007/10/16: + added a parameter to upnpDiscover() in order to allow the use of another + interface than the default multicast interface. + +2007/10/12: + Fixed the creation of symbolic link in Makefile + +2007/10/08: + Added man page + +2007/10/02: + fixed memory bug in GetUPNPUrls() + +2007/10/01: + fixes in the Makefile + Added UPNP_GetIGDFromUrl() and adapted the sample program accordingly. + Added SONAME in the shared library to please debian :) + fixed MS Windows compilation (minissdpd is not available under MS Windows). + +2007/09/25: + small change to Makefile to be able to install in a different location + (default is /usr) + +2007/09/24: + now compiling both shared and static library + +2007/09/19: + Cosmetic changes on upnpc.c + +2007/09/02: + adapting to new miniSSDPd (release version ?) + +2007/08/31: + Usage of miniSSDPd to skip discovery process. + +2007/08/27: + fixed python module to allow compilation with Python older than Python 2.4 + +2007/06/12: + Added a python module. + +2007/05/19: + Fixed compilation under MinGW + +2007/05/15: + fixed a memory leak in AddPortMapping() + Added testupnpreplyparse executable to check the parsing of + upnp soap messages + minixml now ignore namespace prefixes. + +2007/04/26: + upnpc now displays external ip address with -s or -l + +2007/04/11: + changed MINIUPNPC_URL_MAXSIZE to 128 to accomodate the "BT Voyager 210" + +2007/03/19: + cleanup in miniwget.c + +2007/03/01: + Small typo fix... + +2007/01/30: + Now parsing the HTTP header from SOAP responses in order to + get content-length value. + +2007/01/29: + Fixed the Soap Query to speedup the HTTP request. + added some Win32 DLL stuff... + +2007/01/27: + Fixed some WIN32 compatibility issues + +2006/12/14: + Added UPNPIGD_IsConnected() function in miniupnp.c/.h + Added UPNP_GetValidIGD() in miniupnp.c/.h + cleaned upnpc.c main(). now using UPNP_GetValidIGD() + +2006/12/07: + Version 1.0-RC1 released + +2006/12/03: + Minor changes to compile under SunOS/Solaris + +2006/11/30: + made a minixml parser validator program + updated minixml to handle attributes correctly + +2006/11/22: + Added a -r option to the upnpc sample thanks to Alexander Hubmann. + +2006/11/19: + Cleanup code to make it more ANSI C compliant + +2006/11/10: + detect and display local lan address. + +2006/11/04: + Packets and Bytes Sent/Received are now unsigned int. + +2006/11/01: + Bug fix thanks to Giuseppe D'Angelo + +2006/10/31: + C++ compatibility for .h files. + Added a way to get ip Address on the LAN used to reach the IGD. + +2006/10/25: + Added M-SEARCH to the services in the discovery process. + +2006/10/22: + updated the Makefile to use makedepend, added a "make install" + update Makefile + +2006/10/20: + fixing the description url parsing thanks to patch sent by + Wayne Dawe. + Fixed/translated some comments. + Implemented a better discover process, first looking + for IGD then for root devices (as some devices only reply to + M-SEARCH for root devices). + +2006/09/02: + added freeUPNPDevlist() function. + +2006/08/04: + More command line arguments checking + +2006/08/01: + Added the .bat file to compile under Win32 with minGW32 + +2006/07/31: + Fixed the rootdesc parser (igd_desc_parse.c) + +2006/07/20: + parseMSEARCHReply() is now returning the ST: line as well + starting changes to detect several UPnP devices on the network + +2006/07/19: + using GetCommonLinkProperties to get down/upload bitrate + diff --git a/ext/miniupnpc/LICENSE b/ext/miniupnpc/LICENSE new file mode 100644 index 0000000..cb5a060 --- /dev/null +++ b/ext/miniupnpc/LICENSE @@ -0,0 +1,27 @@ +MiniUPnPc +Copyright (c) 2005-2015, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/ext/miniupnpc/MANIFEST.in b/ext/miniupnpc/MANIFEST.in new file mode 100644 index 0000000..54b86f9 --- /dev/null +++ b/ext/miniupnpc/MANIFEST.in @@ -0,0 +1,5 @@ +include README +include miniupnpcmodule.c +include setup.py +include *.h +include libminiupnpc.a diff --git a/ext/miniupnpc/README b/ext/miniupnpc/README new file mode 100644 index 0000000..91535db --- /dev/null +++ b/ext/miniupnpc/README @@ -0,0 +1,64 @@ +Project: miniupnp +Project web page: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ +github: https://github.com/miniupnp/miniupnp +freecode: http://freecode.com/projects/miniupnp +Author: Thomas Bernard +Copyright (c) 2005-2016 Thomas Bernard +This software is subject to the conditions detailed in the +LICENSE file provided within this distribution. + + +* miniUPnP Client - miniUPnPc * + +To compile, simply run 'gmake' (could be 'make' on your system). +Under win32, to compile with MinGW, type "mingw32make.bat". +MS Visual C solution and project files are supplied in the msvc/ subdirectory. + +The compilation is known to work under linux, FreeBSD, +OpenBSD, MacOS X, AmigaOS and cygwin. +The official AmigaOS4.1 SDK was used for AmigaOS4 and GeekGadgets for AmigaOS3. +upx (http://upx.sourceforge.net) is used to compress the win32 .exe files. + +To install the library and headers on the system use : +> su +> make install +> exit + +alternatively, to install into a specific location, use : +> INSTALLPREFIX=/usr/local make install + +upnpc.c is a sample client using the libminiupnpc. +To use the libminiupnpc in your application, link it with +libminiupnpc.a (or .so) and use the following functions found in miniupnpc.h, +upnpcommands.h and miniwget.h : +- upnpDiscover() +- UPNP_GetValidIGD() +- miniwget() +- parserootdesc() +- GetUPNPUrls() +- UPNP_* (calling UPNP methods) + +Note : use #include etc... for the includes +and -lminiupnpc for the link + +Discovery process is speeded up when MiniSSDPd is running on the machine. + + +* Python module * + +you can build a python module with 'make pythonmodule' +and install it with 'make installpythonmodule'. +setup.py (and setupmingw32.py) are included in the distribution. + + +Feel free to contact me if you have any problem : +e-mail : miniupnp@free.fr + +If you are using libminiupnpc in your application, please +send me an email ! + +For any question, you can use the web forum : +http://miniupnp.tuxfamily.org/forum/ + +Bugs should be reported on github : +https://github.com/miniupnp/miniupnp/issues diff --git a/ext/miniupnpc/VERSION b/ext/miniupnpc/VERSION new file mode 100644 index 0000000..cd5ac03 --- /dev/null +++ b/ext/miniupnpc/VERSION @@ -0,0 +1 @@ +2.0 diff --git a/ext/miniupnpc/apiversions.txt b/ext/miniupnpc/apiversions.txt new file mode 100644 index 0000000..9464a86 --- /dev/null +++ b/ext/miniupnpc/apiversions.txt @@ -0,0 +1,172 @@ +$Id: apiversions.txt,v 1.9 2016/01/24 17:24:36 nanard Exp $ + +Differences in API between miniUPnPc versions + +API version 16 + added "status_code" argument to getHTTPResponse(), miniwget() and miniwget_getaddr() + updated macro : + #define MINIUPNPC_API_VERSION 16 + +API version 15 + changed "sameport" argument of upnpDiscover() upnpDiscoverAll() upnpDiscoverDevice() + to "localport". When 0 or 1, behaviour is not changed, but it can take + any other value between 2 and 65535 + Existing programs should be compatible + updated macro : + #define MINIUPNPC_API_VERSION 15 + +API version 14 +miniupnpc.h + add ttl argument to upnpDiscover() upnpDiscoverAll() upnpDiscoverDevice() + upnpDiscoverDevices() + getDevicesFromMiniSSDPD() : + connectToMiniSSDPD() / disconnectFromMiniSSDPD() + requestDevicesFromMiniSSDPD() / receiveDevicesFromMiniSSDPD() + updated macro : + #define MINIUPNPC_API_VERSION 14 + +API version 13 +miniupnpc.h: + add searchalltype param to upnpDiscoverDevices() function + updated macro : + #define MINIUPNPC_API_VERSION 13 + +API version 12 +miniupnpc.h : + add upnpDiscoverAll() / upnpDiscoverDevice() / upnpDiscoverDevices() + functions + updated macros : + #define MINIUPNPC_API_VERSION 12 + +API version 11 + +upnpreplyparse.h / portlistingparse.h : + removed usage of sys/queue.h / bsdqueue.h + +miniupnpc.h: + updated macros : + #define MINIUPNPC_API_VERSION 11 + +====================== miniUPnPc version 1.9 ====================== +API version 10 + +upnpcommands.h: + added argument remoteHost to UPNP_GetSpecificPortMappingEntry() + +miniupnpc.h: + updated macros : + #define MINIUPNPC_VERSION "1.9" + #define MINIUPNPC_API_VERSION 10 + +====================== miniUPnPc version 1.8 ====================== +API version 9 + +miniupnpc.h: + updated macros : + #define MINIUPNPC_VERSION "1.8" + #define MINIUPNPC_API_VERSION 9 + added "unsigned int scope_id;" to struct UPNPDev + added scope_id argument to GetUPNPUrls() + + + +====================== miniUPnPc version 1.7 ====================== +API version 8 + +miniupnpc.h : + add new macros : + #define MINIUPNPC_VERSION "1.7" + #define MINIUPNPC_API_VERSION 8 + add rootdescURL to struct UPNPUrls + + + +====================== miniUPnPc version 1.6 ====================== +API version 8 + +Adding support for IPv6. +igd_desc_parse.h : + struct IGDdatas_service : + add char presentationurl[MINIUPNPC_URL_MAXSIZE]; + struct IGDdatas : + add struct IGDdatas_service IPv6FC; +miniupnpc.h : + new macros : + #define UPNPDISCOVER_SUCCESS (0) + #define UPNPDISCOVER_UNKNOWN_ERROR (-1) + #define UPNPDISCOVER_SOCKET_ERROR (-101) + #define UPNPDISCOVER_MEMORY_ERROR (-102) + simpleUPnPcommand() prototype changed (but is normaly not used by API users) + add arguments ipv6 and error to upnpDiscover() : + struct UPNPDev * + upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock, int sameport, + int ipv6, + int * error); + add controlURL_6FC member to struct UPNPUrls : + struct UPNPUrls { + char * controlURL; + char * ipcondescURL; + char * controlURL_CIF; + char * controlURL_6FC; + }; + +upnpcommands.h : + add leaseDuration argument to UPNP_AddPortMapping() + add desc, enabled and leaseDuration arguments to UPNP_GetSpecificPortMappingEntry() + add UPNP_GetListOfPortMappings() function (IGDv2) + add IGDv2 IPv6 related functions : + UPNP_GetFirewallStatus() + UPNP_GetOutboundPinholeTimeout() + UPNP_AddPinhole() + UPNP_UpdatePinhole() + UPNP_DeletePinhole() + UPNP_CheckPinholeWorking() + UPNP_GetPinholePackets() + + + +====================== miniUPnPc version 1.5 ====================== +API version 5 + +new function : +int UPNPIGD_IsConnected(struct UPNPUrls *, struct IGDdatas *); +new macro in upnpcommands.h : +#define UPNPCOMMAND_HTTP_ERROR + +====================== miniUPnPc version 1.4 ====================== +Same API as version 1.3 + +====================== miniUPnPc version 1.3 ====================== +API version 4 + +Use UNSIGNED_INTEGER type for +UPNP_GetTotalBytesSent(), UPNP_GetTotalBytesReceived(), +UPNP_GetTotalPacketsSent(), UPNP_GetTotalPacketsReceived() +Add remoteHost argument to UPNP_AddPortMapping() and UPNP_DeletePortMapping() + +====================== miniUPnPc version 1.2 ====================== +API version 3 + +added sameport argument to upnpDiscover() +struct UPNPDev * +upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock, int sameport); + +====================== miniUPnPc Version 1.1 ====================== +Same API as 1.0 + + +====================== miniUPnPc Version 1.0 ====================== +API version 2 + + +struct UPNPDev { + struct UPNPDev * pNext; + char * descURL; + char * st; + char buffer[2]; +}; +struct UPNPDev * upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock); + diff --git a/ext/miniupnpc/codelength.h b/ext/miniupnpc/codelength.h new file mode 100644 index 0000000..f5f8e30 --- /dev/null +++ b/ext/miniupnpc/codelength.h @@ -0,0 +1,54 @@ +/* $Id: codelength.h,v 1.5 2015/07/09 12:40:18 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas BERNARD + * copyright (c) 2005-2015 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +#ifndef CODELENGTH_H_INCLUDED +#define CODELENGTH_H_INCLUDED + +/* Encode length by using 7bit per Byte : + * Most significant bit of each byte specifies that the + * following byte is part of the code */ + +/* n : unsigned + * p : unsigned char * + */ +#define DECODELENGTH(n, p) n = 0; \ + do { n = (n << 7) | (*p & 0x7f); } \ + while((*(p++)&0x80) && (n<(1<<25))); + +/* n : unsigned + * READ : function/macro to read one byte (unsigned char) + */ +#define DECODELENGTH_READ(n, READ) \ + n = 0; \ + do { \ + unsigned char c; \ + READ(c); \ + n = (n << 7) | (c & 0x07f); \ + if(!(c&0x80)) break; \ + } while(n<(1<<25)); + +/* n : unsigned + * p : unsigned char * + * p_limit : unsigned char * + */ +#define DECODELENGTH_CHECKLIMIT(n, p, p_limit) \ + n = 0; \ + do { \ + if((p) >= (p_limit)) break; \ + n = (n << 7) | (*(p) & 0x7f); \ + } while((*((p)++)&0x80) && (n<(1<<25))); + + +/* n : unsigned + * p : unsigned char * + */ +#define CODELENGTH(n, p) if(n>=268435456) *(p++) = (n >> 28) | 0x80; \ + if(n>=2097152) *(p++) = (n >> 21) | 0x80; \ + if(n>=16384) *(p++) = (n >> 14) | 0x80; \ + if(n>=128) *(p++) = (n >> 7) | 0x80; \ + *(p++) = n & 0x7f; + +#endif /* CODELENGTH_H_INCLUDED */ diff --git a/ext/miniupnpc/connecthostport.c b/ext/miniupnpc/connecthostport.c new file mode 100644 index 0000000..c12d7bd --- /dev/null +++ b/ext/miniupnpc/connecthostport.c @@ -0,0 +1,264 @@ +/* $Id: connecthostport.c,v 1.16 2016/12/16 08:57:53 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2010-2016 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +/* use getaddrinfo() or gethostbyname() + * uncomment the following line in order to use gethostbyname() */ +#ifdef NO_GETADDRINFO +#define USE_GETHOSTBYNAME +#endif + +#include +#include +#ifdef _WIN32 +#include +#include +#include +#define MAXHOSTNAMELEN 64 +#define snprintf _snprintf +#define herror +#define socklen_t int +#else /* #ifdef _WIN32 */ +#include +#include +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT +#include +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ +#include +#include +#include +#define closesocket close +#include +#include +/* defining MINIUPNPC_IGNORE_EINTR enable the ignore of interruptions + * during the connect() call */ +#define MINIUPNPC_IGNORE_EINTR +#ifndef USE_GETHOSTBYNAME +#include +#include +#endif /* #ifndef USE_GETHOSTBYNAME */ +#endif /* #else _WIN32 */ + +/* definition of PRINT_SOCKET_ERROR */ +#ifdef _WIN32 +#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); +#else +#define PRINT_SOCKET_ERROR(x) perror(x) +#endif + +#if defined(__amigaos__) || defined(__amigaos4__) +#define herror(A) printf("%s\n", A) +#endif + +#include "connecthostport.h" + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +/* connecthostport() + * return a socket connected (TCP) to the host and port + * or -1 in case of error */ +int connecthostport(const char * host, unsigned short port, + unsigned int scope_id) +{ + int s, n; +#ifdef USE_GETHOSTBYNAME + struct sockaddr_in dest; + struct hostent *hp; +#else /* #ifdef USE_GETHOSTBYNAME */ + char tmp_host[MAXHOSTNAMELEN+1]; + char port_str[8]; + struct addrinfo *ai, *p; + struct addrinfo hints; +#endif /* #ifdef USE_GETHOSTBYNAME */ +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT + struct timeval timeout; +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + +#ifdef USE_GETHOSTBYNAME + hp = gethostbyname(host); + if(hp == NULL) + { + herror(host); + return -1; + } + memcpy(&dest.sin_addr, hp->h_addr, sizeof(dest.sin_addr)); + memset(dest.sin_zero, 0, sizeof(dest.sin_zero)); + s = socket(PF_INET, SOCK_STREAM, 0); + if(s < 0) + { + PRINT_SOCKET_ERROR("socket"); + return -1; + } +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT + /* setting a 3 seconds timeout for the connect() call */ + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt SO_RCVTIMEO"); + } + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt SO_SNDTIMEO"); + } +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + dest.sin_family = AF_INET; + dest.sin_port = htons(port); + n = connect(s, (struct sockaddr *)&dest, sizeof(struct sockaddr_in)); +#ifdef MINIUPNPC_IGNORE_EINTR + /* EINTR The system call was interrupted by a signal that was caught + * EINPROGRESS The socket is nonblocking and the connection cannot + * be completed immediately. */ + while(n < 0 && (errno == EINTR || errno == EINPROGRESS)) + { + socklen_t len; + fd_set wset; + int err; + FD_ZERO(&wset); + FD_SET(s, &wset); + if((n = select(s + 1, NULL, &wset, NULL, NULL)) == -1 && errno == EINTR) + continue; + /*len = 0;*/ + /*n = getpeername(s, NULL, &len);*/ + len = sizeof(err); + if(getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { + PRINT_SOCKET_ERROR("getsockopt"); + closesocket(s); + return -1; + } + if(err != 0) { + errno = err; + n = -1; + } + } +#endif /* #ifdef MINIUPNPC_IGNORE_EINTR */ + if(n<0) + { + PRINT_SOCKET_ERROR("connect"); + closesocket(s); + return -1; + } +#else /* #ifdef USE_GETHOSTBYNAME */ + /* use getaddrinfo() instead of gethostbyname() */ + memset(&hints, 0, sizeof(hints)); + /* hints.ai_flags = AI_ADDRCONFIG; */ +#ifdef AI_NUMERICSERV + hints.ai_flags = AI_NUMERICSERV; +#endif + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; /* AF_INET, AF_INET6 or AF_UNSPEC */ + /* hints.ai_protocol = IPPROTO_TCP; */ + snprintf(port_str, sizeof(port_str), "%hu", port); + if(host[0] == '[') + { + /* literal ip v6 address */ + int i, j; + for(i = 0, j = 1; host[j] && (host[j] != ']') && i < MAXHOSTNAMELEN; i++, j++) + { + tmp_host[i] = host[j]; + if(0 == memcmp(host+j, "%25", 3)) /* %25 is just url encoding for '%' */ + j+=2; /* skip "25" */ + } + tmp_host[i] = '\0'; + } + else + { + strncpy(tmp_host, host, MAXHOSTNAMELEN); + } + tmp_host[MAXHOSTNAMELEN] = '\0'; + n = getaddrinfo(tmp_host, port_str, &hints, &ai); + if(n != 0) + { +#ifdef _WIN32 + fprintf(stderr, "getaddrinfo() error : %d\n", n); +#else + fprintf(stderr, "getaddrinfo() error : %s\n", gai_strerror(n)); +#endif + return -1; + } + s = -1; + for(p = ai; p; p = p->ai_next) + { + s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if(s < 0) + continue; + if(p->ai_addr->sa_family == AF_INET6 && scope_id > 0) { + struct sockaddr_in6 * addr6 = (struct sockaddr_in6 *)p->ai_addr; + addr6->sin6_scope_id = scope_id; + } +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT + /* setting a 3 seconds timeout for the connect() call */ + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt"); + } + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt"); + } +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + n = connect(s, p->ai_addr, p->ai_addrlen); +#ifdef MINIUPNPC_IGNORE_EINTR + /* EINTR The system call was interrupted by a signal that was caught + * EINPROGRESS The socket is nonblocking and the connection cannot + * be completed immediately. */ + while(n < 0 && (errno == EINTR || errno == EINPROGRESS)) + { + socklen_t len; + fd_set wset; + int err; + FD_ZERO(&wset); + FD_SET(s, &wset); + if((n = select(s + 1, NULL, &wset, NULL, NULL)) == -1 && errno == EINTR) + continue; + /*len = 0;*/ + /*n = getpeername(s, NULL, &len);*/ + len = sizeof(err); + if(getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { + PRINT_SOCKET_ERROR("getsockopt"); + closesocket(s); + freeaddrinfo(ai); + return -1; + } + if(err != 0) { + errno = err; + n = -1; + } + } +#endif /* #ifdef MINIUPNPC_IGNORE_EINTR */ + if(n < 0) + { + closesocket(s); + continue; + } + else + { + break; + } + } + freeaddrinfo(ai); + if(s < 0) + { + PRINT_SOCKET_ERROR("socket"); + return -1; + } + if(n < 0) + { + PRINT_SOCKET_ERROR("connect"); + return -1; + } +#endif /* #ifdef USE_GETHOSTBYNAME */ + return s; +} + diff --git a/ext/miniupnpc/connecthostport.h b/ext/miniupnpc/connecthostport.h new file mode 100644 index 0000000..56941d6 --- /dev/null +++ b/ext/miniupnpc/connecthostport.h @@ -0,0 +1,18 @@ +/* $Id: connecthostport.h,v 1.3 2012/09/27 15:42:10 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ + * Author: Thomas Bernard + * Copyright (c) 2010-2012 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef CONNECTHOSTPORT_H_INCLUDED +#define CONNECTHOSTPORT_H_INCLUDED + +/* connecthostport() + * return a socket connected (TCP) to the host and port + * or -1 in case of error */ +int connecthostport(const char * host, unsigned short port, + unsigned int scope_id); + +#endif + diff --git a/ext/miniupnpc/external-ip.sh b/ext/miniupnpc/external-ip.sh new file mode 100755 index 0000000..965d86b --- /dev/null +++ b/ext/miniupnpc/external-ip.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# $Id: external-ip.sh,v 1.1 2010/08/05 12:57:41 nanard Exp $ +# (c) 2010 Reuben Hawkins +upnpc -s | grep ExternalIPAddress | sed 's/[^0-9\.]//g' diff --git a/ext/miniupnpc/igd_desc_parse.c b/ext/miniupnpc/igd_desc_parse.c new file mode 100644 index 0000000..d2999ad --- /dev/null +++ b/ext/miniupnpc/igd_desc_parse.c @@ -0,0 +1,123 @@ +/* $Id: igd_desc_parse.c,v 1.17 2015/09/15 13:30:04 nanard Exp $ */ +/* Project : miniupnp + * http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2015 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include "igd_desc_parse.h" +#include +#include + +/* Start element handler : + * update nesting level counter and copy element name */ +void IGDstartelt(void * d, const char * name, int l) +{ + struct IGDdatas * datas = (struct IGDdatas *)d; + if(l >= MINIUPNPC_URL_MAXSIZE) + l = MINIUPNPC_URL_MAXSIZE-1; + memcpy(datas->cureltname, name, l); + datas->cureltname[l] = '\0'; + datas->level++; + if( (l==7) && !memcmp(name, "service", l) ) { + datas->tmp.controlurl[0] = '\0'; + datas->tmp.eventsuburl[0] = '\0'; + datas->tmp.scpdurl[0] = '\0'; + datas->tmp.servicetype[0] = '\0'; + } +} + +#define COMPARE(str, cstr) (0==memcmp(str, cstr, sizeof(cstr) - 1)) + +/* End element handler : + * update nesting level counter and update parser state if + * service element is parsed */ +void IGDendelt(void * d, const char * name, int l) +{ + struct IGDdatas * datas = (struct IGDdatas *)d; + datas->level--; + /*printf("endelt %2d %.*s\n", datas->level, l, name);*/ + if( (l==7) && !memcmp(name, "service", l) ) + { + if(COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:")) { + memcpy(&datas->CIF, &datas->tmp, sizeof(struct IGDdatas_service)); + } else if(COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANIPv6FirewallControl:")) { + memcpy(&datas->IPv6FC, &datas->tmp, sizeof(struct IGDdatas_service)); + } else if(COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANIPConnection:") + || COMPARE(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANPPPConnection:") ) { + if(datas->first.servicetype[0] == '\0') { + memcpy(&datas->first, &datas->tmp, sizeof(struct IGDdatas_service)); + } else { + memcpy(&datas->second, &datas->tmp, sizeof(struct IGDdatas_service)); + } + } + } +} + +/* Data handler : + * copy data depending on the current element name and state */ +void IGDdata(void * d, const char * data, int l) +{ + struct IGDdatas * datas = (struct IGDdatas *)d; + char * dstmember = 0; + /*printf("%2d %s : %.*s\n", + datas->level, datas->cureltname, l, data); */ + if( !strcmp(datas->cureltname, "URLBase") ) + dstmember = datas->urlbase; + else if( !strcmp(datas->cureltname, "presentationURL") ) + dstmember = datas->presentationurl; + else if( !strcmp(datas->cureltname, "serviceType") ) + dstmember = datas->tmp.servicetype; + else if( !strcmp(datas->cureltname, "controlURL") ) + dstmember = datas->tmp.controlurl; + else if( !strcmp(datas->cureltname, "eventSubURL") ) + dstmember = datas->tmp.eventsuburl; + else if( !strcmp(datas->cureltname, "SCPDURL") ) + dstmember = datas->tmp.scpdurl; +/* else if( !strcmp(datas->cureltname, "deviceType") ) + dstmember = datas->devicetype_tmp;*/ + if(dstmember) + { + if(l>=MINIUPNPC_URL_MAXSIZE) + l = MINIUPNPC_URL_MAXSIZE-1; + memcpy(dstmember, data, l); + dstmember[l] = '\0'; + } +} + +#ifdef DEBUG +void printIGD(struct IGDdatas * d) +{ + printf("urlbase = '%s'\n", d->urlbase); + printf("WAN Device (Common interface config) :\n"); + /*printf(" deviceType = '%s'\n", d->CIF.devicetype);*/ + printf(" serviceType = '%s'\n", d->CIF.servicetype); + printf(" controlURL = '%s'\n", d->CIF.controlurl); + printf(" eventSubURL = '%s'\n", d->CIF.eventsuburl); + printf(" SCPDURL = '%s'\n", d->CIF.scpdurl); + printf("primary WAN Connection Device (IP or PPP Connection):\n"); + /*printf(" deviceType = '%s'\n", d->first.devicetype);*/ + printf(" servicetype = '%s'\n", d->first.servicetype); + printf(" controlURL = '%s'\n", d->first.controlurl); + printf(" eventSubURL = '%s'\n", d->first.eventsuburl); + printf(" SCPDURL = '%s'\n", d->first.scpdurl); + printf("secondary WAN Connection Device (IP or PPP Connection):\n"); + /*printf(" deviceType = '%s'\n", d->second.devicetype);*/ + printf(" servicetype = '%s'\n", d->second.servicetype); + printf(" controlURL = '%s'\n", d->second.controlurl); + printf(" eventSubURL = '%s'\n", d->second.eventsuburl); + printf(" SCPDURL = '%s'\n", d->second.scpdurl); + printf("WAN IPv6 Firewall Control :\n"); + /*printf(" deviceType = '%s'\n", d->IPv6FC.devicetype);*/ + printf(" servicetype = '%s'\n", d->IPv6FC.servicetype); + printf(" controlURL = '%s'\n", d->IPv6FC.controlurl); + printf(" eventSubURL = '%s'\n", d->IPv6FC.eventsuburl); + printf(" SCPDURL = '%s'\n", d->IPv6FC.scpdurl); +} +#endif /* DEBUG */ + diff --git a/ext/miniupnpc/igd_desc_parse.h b/ext/miniupnpc/igd_desc_parse.h new file mode 100644 index 0000000..0de546b --- /dev/null +++ b/ext/miniupnpc/igd_desc_parse.h @@ -0,0 +1,49 @@ +/* $Id: igd_desc_parse.h,v 1.12 2014/11/17 17:19:13 nanard Exp $ */ +/* Project : miniupnp + * http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2014 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef IGD_DESC_PARSE_H_INCLUDED +#define IGD_DESC_PARSE_H_INCLUDED + +/* Structure to store the result of the parsing of UPnP + * descriptions of Internet Gateway Devices */ +#define MINIUPNPC_URL_MAXSIZE (128) +struct IGDdatas_service { + char controlurl[MINIUPNPC_URL_MAXSIZE]; + char eventsuburl[MINIUPNPC_URL_MAXSIZE]; + char scpdurl[MINIUPNPC_URL_MAXSIZE]; + char servicetype[MINIUPNPC_URL_MAXSIZE]; + /*char devicetype[MINIUPNPC_URL_MAXSIZE];*/ +}; + +struct IGDdatas { + char cureltname[MINIUPNPC_URL_MAXSIZE]; + char urlbase[MINIUPNPC_URL_MAXSIZE]; + char presentationurl[MINIUPNPC_URL_MAXSIZE]; + int level; + /*int state;*/ + /* "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */ + struct IGDdatas_service CIF; + /* "urn:schemas-upnp-org:service:WANIPConnection:1" + * "urn:schemas-upnp-org:service:WANPPPConnection:1" */ + struct IGDdatas_service first; + /* if both WANIPConnection and WANPPPConnection are present */ + struct IGDdatas_service second; + /* "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1" */ + struct IGDdatas_service IPv6FC; + /* tmp */ + struct IGDdatas_service tmp; +}; + +void IGDstartelt(void *, const char *, int); +void IGDendelt(void *, const char *, int); +void IGDdata(void *, const char *, int); +#ifdef DEBUG +void printIGD(struct IGDdatas *); +#endif /* DEBUG */ + +#endif /* IGD_DESC_PARSE_H_INCLUDED */ diff --git a/ext/miniupnpc/listdevices.c b/ext/miniupnpc/listdevices.c new file mode 100644 index 0000000..a93c29f --- /dev/null +++ b/ext/miniupnpc/listdevices.c @@ -0,0 +1,110 @@ +/* $Id: listdevices.c,v 1.7 2015/10/08 16:15:47 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2013-2015 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include +#include +#include +#ifdef _WIN32 +#include +#endif /* _WIN32 */ +#include "miniupnpc.h" + +int main(int argc, char * * argv) +{ + const char * searched_device = NULL; + const char * * searched_devices = NULL; + const char * multicastif = 0; + const char * minissdpdpath = 0; + int ipv6 = 0; + unsigned char ttl = 2; + int error = 0; + struct UPNPDev * devlist = 0; + struct UPNPDev * dev; + int i; + +#ifdef _WIN32 + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2,2), &wsaData); + if(nResult != NO_ERROR) + { + fprintf(stderr, "WSAStartup() failed.\n"); + return -1; + } +#endif + + for(i = 1; i < argc; i++) { + if(strcmp(argv[i], "-6") == 0) + ipv6 = 1; + else if(strcmp(argv[i], "-d") == 0) { + if(++i >= argc) { + fprintf(stderr, "%s option needs one argument\n", "-d"); + return 1; + } + searched_device = argv[i]; + } else if(strcmp(argv[i], "-t") == 0) { + if(++i >= argc) { + fprintf(stderr, "%s option needs one argument\n", "-t"); + return 1; + } + ttl = (unsigned char)atoi(argv[i]); + } else if(strcmp(argv[i], "-l") == 0) { + if(++i >= argc) { + fprintf(stderr, "-l option needs at least one argument\n"); + return 1; + } + searched_devices = (const char * *)(argv + i); + break; + } else if(strcmp(argv[i], "-m") == 0) { + if(++i >= argc) { + fprintf(stderr, "-m option needs one argument\n"); + return 1; + } + multicastif = argv[i]; + } else { + printf("usage : %s [options] [-l ...]\n", argv[0]); + printf("options :\n"); + printf(" -6 : use IPv6\n"); + printf(" -m address/ifname : network interface to use for multicast\n"); + printf(" -d : search only for this type of device\n"); + printf(" -l ... : search only for theses types of device\n"); + printf(" -t ttl : set multicast TTL. Default value is 2.\n"); + printf(" -h : this help\n"); + return 1; + } + } + + if(searched_device) { + printf("searching UPnP device type %s\n", searched_device); + devlist = upnpDiscoverDevice(searched_device, + 2000, multicastif, minissdpdpath, + 0/*localport*/, ipv6, ttl, &error); + } else if(searched_devices) { + printf("searching UPnP device types :\n"); + for(i = 0; searched_devices[i]; i++) + printf("\t%s\n", searched_devices[i]); + devlist = upnpDiscoverDevices(searched_devices, + 2000, multicastif, minissdpdpath, + 0/*localport*/, ipv6, ttl, &error, 1); + } else { + printf("searching all UPnP devices\n"); + devlist = upnpDiscoverAll(2000, multicastif, minissdpdpath, + 0/*localport*/, ipv6, ttl, &error); + } + if(devlist) { + for(dev = devlist, i = 1; dev != NULL; dev = dev->pNext, i++) { + printf("%3d: %-48s\n", i, dev->st); + printf(" %s\n", dev->descURL); + printf(" %s\n", dev->usn); + } + freeUPNPDevlist(devlist); + } else { + printf("no device found.\n"); + } + + return 0; +} + diff --git a/ext/miniupnpc/mingw32make.bat b/ext/miniupnpc/mingw32make.bat new file mode 100644 index 0000000..c5d3cc4 --- /dev/null +++ b/ext/miniupnpc/mingw32make.bat @@ -0,0 +1,8 @@ +@mingw32-make -f Makefile.mingw %1 +@if errorlevel 1 goto end +@if not exist upnpc-static.exe goto end +@strip upnpc-static.exe +@upx --best upnpc-static.exe +@strip upnpc-shared.exe +@upx --best upnpc-shared.exe +:end diff --git a/ext/miniupnpc/minihttptestserver.c b/ext/miniupnpc/minihttptestserver.c new file mode 100644 index 0000000..d95dd7c --- /dev/null +++ b/ext/miniupnpc/minihttptestserver.c @@ -0,0 +1,659 @@ +/* $Id: minihttptestserver.c,v 1.20 2016/12/16 08:54:55 nanard Exp $ */ +/* Project : miniUPnP + * Author : Thomas Bernard + * Copyright (c) 2011-2016 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef INADDR_LOOPBACK +#define INADDR_LOOPBACK 0x7f000001 +#endif + +#define CRAP_LENGTH (2048) + +volatile sig_atomic_t quit = 0; +volatile sig_atomic_t child_to_wait_for = 0; + +/** + * signal handler for SIGCHLD (child status has changed) + */ +void handle_signal_chld(int sig) +{ + (void)sig; + /* printf("handle_signal_chld(%d)\n", sig); */ + ++child_to_wait_for; +} + +/** + * signal handler for SIGINT (CRTL C) + */ +void handle_signal_int(int sig) +{ + (void)sig; + /* printf("handle_signal_int(%d)\n", sig); */ + quit = 1; +} + +/** + * build a text/plain content of the specified length + */ +void build_content(char * p, int n) +{ + char line_buffer[80]; + int k; + int i = 0; + + while(n > 0) { + k = snprintf(line_buffer, sizeof(line_buffer), + "%04d_ABCDEFGHIJKL_This_line_is_64_bytes_long_ABCDEFGHIJKL_%04d\r\n", + i, i); + if(k != 64) { + fprintf(stderr, "snprintf() returned %d in build_content()\n", k); + } + ++i; + if(n >= 64) { + memcpy(p, line_buffer, 64); + p += 64; + n -= 64; + } else { + memcpy(p, line_buffer, n); + p += n; + n = 0; + } + } +} + +/** + * build crappy content + */ +void build_crap(char * p, int n) +{ + static const char crap[] = "_CRAP_\r\n"; + int i; + + while(n > 0) { + i = sizeof(crap) - 1; + if(i > n) + i = n; + memcpy(p, crap, i); + p += i; + n -= i; + } +} + +/** + * build chunked response. + * return a malloc'ed buffer + */ +char * build_chunked_response(int content_length, int * response_len) +{ + char * response_buffer; + char * content_buffer; + int buffer_length; + int i, n; + + /* allocate to have some margin */ + buffer_length = 256 + content_length + (content_length >> 4); + response_buffer = malloc(buffer_length); + if(response_buffer == NULL) + return NULL; + *response_len = snprintf(response_buffer, buffer_length, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n"); + + /* build the content */ + content_buffer = malloc(content_length); + if(content_buffer == NULL) { + free(response_buffer); + return NULL; + } + build_content(content_buffer, content_length); + + /* chunk it */ + i = 0; + while(i < content_length) { + n = (rand() % 199) + 1; + if(i + n > content_length) { + n = content_length - i; + } + /* TODO : check buffer size ! */ + *response_len += snprintf(response_buffer + *response_len, + buffer_length - *response_len, + "%x\r\n", n); + memcpy(response_buffer + *response_len, content_buffer + i, n); + *response_len += n; + i += n; + response_buffer[(*response_len)++] = '\r'; + response_buffer[(*response_len)++] = '\n'; + } + /* the last chunk : "0\r\n" a empty body and then + * the final "\r\n" */ + memcpy(response_buffer + *response_len, "0\r\n\r\n", 5); + *response_len += 5; + free(content_buffer); + + printf("resp_length=%d buffer_length=%d content_length=%d\n", + *response_len, buffer_length, content_length); + return response_buffer; +} + +/* favicon.ico generator */ +#ifdef OLD_HEADER +#define FAVICON_LENGTH (6 + 16 + 12 + 8 + 32 * 4) +#else +#define FAVICON_LENGTH (6 + 16 + 40 + 8 + 32 * 4) +#endif +void build_favicon_content(char * p, int n) +{ + int i; + if(n < FAVICON_LENGTH) + return; + /* header : 6 bytes */ + *p++ = 0; + *p++ = 0; + *p++ = 1; /* type : ICO */ + *p++ = 0; + *p++ = 1; /* number of images in file */ + *p++ = 0; + /* image directory (1 entry) : 16 bytes */ + *p++ = 16; /* width */ + *p++ = 16; /* height */ + *p++ = 2; /* number of colors in the palette. 0 = no palette */ + *p++ = 0; /* reserved */ + *p++ = 1; /* color planes */ + *p++ = 0; /* " */ + *p++ = 1; /* bpp */ + *p++ = 0; /* " */ +#ifdef OLD_HEADER + *p++ = 12 + 8 + 32 * 4; /* bmp size */ +#else + *p++ = 40 + 8 + 32 * 4; /* bmp size */ +#endif + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 6 + 16; /* bmp offset */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + /* BMP */ +#ifdef OLD_HEADER + /* BITMAPCOREHEADER */ + *p++ = 12; /* size of this header */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 16; /* width */ + *p++ = 0; /* " */ + *p++ = 16 * 2; /* height x 2 ! */ + *p++ = 0; /* " */ + *p++ = 1; /* color planes */ + *p++ = 0; /* " */ + *p++ = 1; /* bpp */ + *p++ = 0; /* " */ +#else + /* BITMAPINFOHEADER */ + *p++ = 40; /* size of this header */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 16; /* width */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 16 * 2; /* height x 2 ! */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 0; /* " */ + *p++ = 1; /* color planes */ + *p++ = 0; /* " */ + *p++ = 1; /* bpp */ + *p++ = 0; /* " */ + /* compression method, image size, ppm x, ppm y */ + /* colors in the palette ? */ + /* important colors */ + for(i = 4 * 6; i > 0; --i) + *p++ = 0; +#endif + /* palette */ + *p++ = 0; /* b */ + *p++ = 0; /* g */ + *p++ = 0; /* r */ + *p++ = 0; /* reserved */ + *p++ = 255; /* b */ + *p++ = 255; /* g */ + *p++ = 255; /* r */ + *p++ = 0; /* reserved */ + /* pixel data */ + for(i = 16; i > 0; --i) { + if(i & 1) { + *p++ = 0125; + *p++ = 0125; + } else { + *p++ = 0252; + *p++ = 0252; + } + *p++ = 0; + *p++ = 0; + } + /* Opacity MASK */ + for(i = 16 * 4; i > 0; --i) { + *p++ = 0; + } +} + +enum modes { + MODE_INVALID, MODE_CHUNKED, MODE_ADDCRAP, MODE_NORMAL, MODE_FAVICON +}; + +const struct { + const enum modes mode; + const char * text; +} modes_array[] = { + {MODE_CHUNKED, "chunked"}, + {MODE_ADDCRAP, "addcrap"}, + {MODE_NORMAL, "normal"}, + {MODE_FAVICON, "favicon.ico"}, + {MODE_INVALID, NULL} +}; + +/** + * write the response with random behaviour ! + */ +void send_response(int c, const char * buffer, int len) +{ + int n; + while(len > 0) { + n = (rand() % 99) + 1; + if(n > len) + n = len; + n = write(c, buffer, n); + if(n < 0) { + if(errno != EINTR) { + perror("write"); + return; + } + /* if errno == EINTR, try again */ + } else { + len -= n; + buffer += n; + } + usleep(10000); /* 10ms */ + } +} + +/** + * handle the HTTP connection + */ +void handle_http_connection(int c) +{ + char request_buffer[2048]; + int request_len = 0; + int headers_found = 0; + int n, i; + char request_method[16]; + char request_uri[256]; + char http_version[16]; + char * p; + char * response_buffer; + int response_len; + enum modes mode; + int content_length = 16*1024; + + /* read the request */ + while(request_len < (int)sizeof(request_buffer) && !headers_found) { + n = read(c, + request_buffer + request_len, + sizeof(request_buffer) - request_len); + if(n < 0) { + if(errno == EINTR) + continue; + perror("read"); + return; + } else if(n==0) { + /* remote host closed the connection */ + break; + } else { + request_len += n; + for(i = 0; i < request_len - 3; i++) { + if(0 == memcmp(request_buffer + i, "\r\n\r\n", 4)) { + /* found the end of headers */ + headers_found = 1; + break; + } + } + } + } + if(!headers_found) { + /* error */ + printf("no HTTP header found in the request\n"); + return; + } + printf("headers :\n%.*s", request_len, request_buffer); + /* the request have been received, now parse the request line */ + p = request_buffer; + for(i = 0; i < (int)sizeof(request_method) - 1; i++) { + if(*p == ' ' || *p == '\r') + break; + request_method[i] = *p; + ++p; + } + request_method[i] = '\0'; + while(*p == ' ') + p++; + for(i = 0; i < (int)sizeof(request_uri) - 1; i++) { + if(*p == ' ' || *p == '\r') + break; + request_uri[i] = *p; + ++p; + } + request_uri[i] = '\0'; + while(*p == ' ') + p++; + for(i = 0; i < (int)sizeof(http_version) - 1; i++) { + if(*p == ' ' || *p == '\r') + break; + http_version[i] = *p; + ++p; + } + http_version[i] = '\0'; + printf("Method = %s, URI = %s, %s\n", + request_method, request_uri, http_version); + /* check if the request method is allowed */ + if(0 != strcmp(request_method, "GET")) { + const char response405[] = "HTTP/1.1 405 Method Not Allowed\r\n" + "Allow: GET\r\n\r\n"; + const char * pc; + /* 405 Method Not Allowed */ + /* The response MUST include an Allow header containing a list + * of valid methods for the requested resource. */ + n = sizeof(response405) - 1; + pc = response405; + while(n > 0) { + i = write(c, pc, n); + if(i<0) { + if(errno != EINTR) { + perror("write"); + return; + } + } else { + n -= i; + pc += i; + } + } + return; + } + + mode = MODE_INVALID; + /* use the request URI to know what to do */ + for(i = 0; modes_array[i].mode != MODE_INVALID; i++) { + if(strstr(request_uri, modes_array[i].text)) { + mode = modes_array[i].mode; /* found */ + break; + } + } + + switch(mode) { + case MODE_CHUNKED: + response_buffer = build_chunked_response(content_length, &response_len); + break; + case MODE_ADDCRAP: + response_len = content_length+256; + response_buffer = malloc(response_len); + if(!response_buffer) + break; + n = snprintf(response_buffer, response_len, + "HTTP/1.1 200 OK\r\n" + "Server: minihttptestserver\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %d\r\n" + "\r\n", content_length); + response_len = content_length+n+CRAP_LENGTH; + p = realloc(response_buffer, response_len); + if(p == NULL) { + /* error 500 */ + free(response_buffer); + response_buffer = NULL; + break; + } + response_buffer = p; + build_content(response_buffer + n, content_length); + build_crap(response_buffer + n + content_length, CRAP_LENGTH); + break; + case MODE_FAVICON: + content_length = FAVICON_LENGTH; + response_len = content_length + 256; + response_buffer = malloc(response_len); + if(!response_buffer) + break; + n = snprintf(response_buffer, response_len, + "HTTP/1.1 200 OK\r\n" + "Server: minihttptestserver\r\n" + "Content-Type: image/vnd.microsoft.icon\r\n" + "Content-Length: %d\r\n" + "\r\n", content_length); + /* image/x-icon */ + build_favicon_content(response_buffer + n, content_length); + response_len = content_length + n; + break; + default: + response_len = content_length+256; + response_buffer = malloc(response_len); + if(!response_buffer) + break; + n = snprintf(response_buffer, response_len, + "HTTP/1.1 200 OK\r\n" + "Server: minihttptestserver\r\n" + "Content-Type: text/plain\r\n" + "\r\n"); + response_len = content_length+n; + p = realloc(response_buffer, response_len); + if(p == NULL) { + /* Error 500 */ + free(response_buffer); + response_buffer = NULL; + break; + } + response_buffer = p; + build_content(response_buffer + n, response_len - n); + } + + if(response_buffer) { + send_response(c, response_buffer, response_len); + free(response_buffer); + } else { + /* Error 500 */ + } +} + +/** + */ +int main(int argc, char * * argv) { + int ipv6 = 0; + int s, c, i; + unsigned short port = 0; + struct sockaddr_storage server_addr; + socklen_t server_addrlen; + struct sockaddr_storage client_addr; + socklen_t client_addrlen; + pid_t pid; + int child = 0; + int status; + const char * expected_file_name = NULL; + struct sigaction sa; + + for(i = 1; i < argc; i++) { + if(argv[i][0] == '-') { + switch(argv[i][1]) { + case '6': + ipv6 = 1; + break; + case 'e': + /* write expected file ! */ + expected_file_name = argv[++i]; + break; + case 'p': + /* port */ + if(++i < argc) { + port = (unsigned short)atoi(argv[i]); + } + break; + default: + fprintf(stderr, "unknown command line switch '%s'\n", argv[i]); + } + } else { + fprintf(stderr, "unkown command line argument '%s'\n", argv[i]); + } + } + + srand(time(NULL)); + + memset(&sa, 0, sizeof(struct sigaction)); + + /*signal(SIGCHLD, handle_signal_chld);*/ + sa.sa_handler = handle_signal_chld; + if(sigaction(SIGCHLD, &sa, NULL) < 0) { + perror("sigaction"); + return 1; + } + /*signal(SIGINT, handle_signal_int);*/ + sa.sa_handler = handle_signal_int; + if(sigaction(SIGINT, &sa, NULL) < 0) { + perror("sigaction"); + return 1; + } + + s = socket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0); + if(s < 0) { + perror("socket"); + return 1; + } + memset(&server_addr, 0, sizeof(struct sockaddr_storage)); + memset(&client_addr, 0, sizeof(struct sockaddr_storage)); + if(ipv6) { + struct sockaddr_in6 * addr = (struct sockaddr_in6 *)&server_addr; + addr->sin6_family = AF_INET6; + addr->sin6_port = htons(port); + addr->sin6_addr = in6addr_loopback; + } else { + struct sockaddr_in * addr = (struct sockaddr_in *)&server_addr; + addr->sin_family = AF_INET; + addr->sin_port = htons(port); + addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } + if(bind(s, (struct sockaddr *)&server_addr, + ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) < 0) { + perror("bind"); + return 1; + } + if(listen(s, 5) < 0) { + perror("listen"); + } + if(port == 0) { + server_addrlen = sizeof(struct sockaddr_storage); + if(getsockname(s, (struct sockaddr *)&server_addr, &server_addrlen) < 0) { + perror("getsockname"); + return 1; + } + if(ipv6) { + struct sockaddr_in6 * addr = (struct sockaddr_in6 *)&server_addr; + port = ntohs(addr->sin6_port); + } else { + struct sockaddr_in * addr = (struct sockaddr_in *)&server_addr; + port = ntohs(addr->sin_port); + } + printf("Listening on port %hu\n", port); + fflush(stdout); + } + + /* write expected file */ + if(expected_file_name) { + FILE * f; + f = fopen(expected_file_name, "wb"); + if(f) { + char * buffer; + buffer = malloc(16*1024); + if(buffer == NULL) { + fprintf(stderr, "memory allocation error\n"); + } else { + build_content(buffer, 16*1024); + i = fwrite(buffer, 1, 16*1024, f); + if(i != 16*1024) { + fprintf(stderr, "error writing to file %s : %dbytes written (out of %d)\n", expected_file_name, i, 16*1024); + } + free(buffer); + } + fclose(f); + } else { + fprintf(stderr, "error opening file %s for writing\n", expected_file_name); + } + } + + /* fork() loop */ + while(!child && !quit) { + while(child_to_wait_for > 0) { + pid = wait(&status); + if(pid < 0) { + perror("wait"); + } else { + printf("child(%d) terminated with status %d\n", (int)pid, status); + } + --child_to_wait_for; + } + client_addrlen = sizeof(struct sockaddr_storage); + c = accept(s, (struct sockaddr *)&client_addr, + &client_addrlen); + if(c < 0) { + if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + continue; + perror("accept"); + return 1; + } + printf("accept...\n"); + pid = fork(); + if(pid < 0) { + perror("fork"); + return 1; + } else if(pid == 0) { + /* child */ + child = 1; + close(s); + s = -1; + handle_http_connection(c); + } + close(c); + } + if(s >= 0) { + close(s); + s = -1; + } + if(!child) { + while(child_to_wait_for > 0) { + pid = wait(&status); + if(pid < 0) { + perror("wait"); + } else { + printf("child(%d) terminated with status %d\n", (int)pid, status); + } + --child_to_wait_for; + } + printf("Bye...\n"); + } + return 0; +} + diff --git a/ext/miniupnpc/minisoap.c b/ext/miniupnpc/minisoap.c new file mode 100644 index 0000000..7aa0213 --- /dev/null +++ b/ext/miniupnpc/minisoap.c @@ -0,0 +1,128 @@ +/* $Id: minisoap.c,v 1.24 2015/10/26 17:05:07 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2015 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * + * Minimal SOAP implementation for UPnP protocol. + */ +#include +#include +#ifdef _WIN32 +#include +#include +#define snprintf _snprintf +#else +#include +#include +#include +#endif +#include "minisoap.h" + +#ifdef _WIN32 +#define OS_STRING "Win32" +#define MINIUPNPC_VERSION_STRING "2.0" +#define UPNP_VERSION_STRING "UPnP/1.1" +#endif + +/* only for malloc */ +#include + +#ifdef _WIN32 +#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); +#else +#define PRINT_SOCKET_ERROR(x) perror(x) +#endif + +/* httpWrite sends the headers and the body to the socket + * and returns the number of bytes sent */ +static int +httpWrite(int fd, const char * body, int bodysize, + const char * headers, int headerssize) +{ + int n = 0; + /*n = write(fd, headers, headerssize);*/ + /*if(bodysize>0) + n += write(fd, body, bodysize);*/ + /* Note : my old linksys router only took into account + * soap request that are sent into only one packet */ + char * p; + /* TODO: AVOID MALLOC, we could use writev() for that */ + p = malloc(headerssize+bodysize); + if(!p) + return -1; + memcpy(p, headers, headerssize); + memcpy(p+headerssize, body, bodysize); + /*n = write(fd, p, headerssize+bodysize);*/ + n = send(fd, p, headerssize+bodysize, 0); + if(n<0) { + PRINT_SOCKET_ERROR("send"); + } + /* disable send on the socket */ + /* draytek routers dont seems to like that... */ +#if 0 +#ifdef _WIN32 + if(shutdown(fd, SD_SEND)<0) { +#else + if(shutdown(fd, SHUT_WR)<0) { /*SD_SEND*/ +#endif + PRINT_SOCKET_ERROR("shutdown"); + } +#endif + free(p); + return n; +} + +/* self explanatory */ +int soapPostSubmit(int fd, + const char * url, + const char * host, + unsigned short port, + const char * action, + const char * body, + const char * httpversion) +{ + int bodysize; + char headerbuf[512]; + int headerssize; + char portstr[8]; + bodysize = (int)strlen(body); + /* We are not using keep-alive HTTP connections. + * HTTP/1.1 needs the header Connection: close to do that. + * This is the default with HTTP/1.0 + * Using HTTP/1.1 means we need to support chunked transfer-encoding : + * When using HTTP/1.1, the router "BiPAC 7404VNOX" always use chunked + * transfer encoding. */ + /* Connection: Close is normally there only in HTTP/1.1 but who knows */ + portstr[0] = '\0'; + if(port != 80) + snprintf(portstr, sizeof(portstr), ":%hu", port); + headerssize = snprintf(headerbuf, sizeof(headerbuf), + "POST %s HTTP/%s\r\n" + "Host: %s%s\r\n" + "User-Agent: " OS_STRING ", " UPNP_VERSION_STRING ", MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" + "Content-Length: %d\r\n" + "Content-Type: text/xml\r\n" + "SOAPAction: \"%s\"\r\n" + "Connection: Close\r\n" + "Cache-Control: no-cache\r\n" /* ??? */ + "Pragma: no-cache\r\n" + "\r\n", + url, httpversion, host, portstr, bodysize, action); + if ((unsigned int)headerssize >= sizeof(headerbuf)) + return -1; +#ifdef DEBUG + /*printf("SOAP request : headersize=%d bodysize=%d\n", + headerssize, bodysize); + */ + printf("SOAP request : POST %s HTTP/%s - Host: %s%s\n", + url, httpversion, host, portstr); + printf("SOAPAction: \"%s\" - Content-Length: %d\n", action, bodysize); + printf("Headers :\n%s", headerbuf); + printf("Body :\n%s\n", body); +#endif + return httpWrite(fd, body, bodysize, headerbuf, headerssize); +} + + diff --git a/ext/miniupnpc/minisoap.h b/ext/miniupnpc/minisoap.h new file mode 100644 index 0000000..14c859d --- /dev/null +++ b/ext/miniupnpc/minisoap.h @@ -0,0 +1,15 @@ +/* $Id: minisoap.h,v 1.5 2012/09/27 15:42:10 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ +#ifndef MINISOAP_H_INCLUDED +#define MINISOAP_H_INCLUDED + +/*int httpWrite(int, const char *, int, const char *);*/ +int soapPostSubmit(int, const char *, const char *, unsigned short, + const char *, const char *, const char *); + +#endif + diff --git a/ext/miniupnpc/minissdpc.c b/ext/miniupnpc/minissdpc.c new file mode 100644 index 0000000..06b11e8 --- /dev/null +++ b/ext/miniupnpc/minissdpc.c @@ -0,0 +1,875 @@ +/* $Id: minissdpc.c,v 1.33 2016/12/16 08:57:20 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp + * Web : http://miniupnp.free.fr/ + * Author : Thomas BERNARD + * copyright (c) 2005-2016 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +/*#include */ +#include +#include +#include +#include +#if defined (__NetBSD__) +#include +#endif +#if defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__) +#ifdef _WIN32 +#include +#include +#include +#include +#include +#define snprintf _snprintf +#if !defined(_MSC_VER) +#include +#else /* !defined(_MSC_VER) */ +typedef unsigned short uint16_t; +#endif /* !defined(_MSC_VER) */ +#ifndef strncasecmp +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#define strncasecmp _memicmp +#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#define strncasecmp memicmp +#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#endif /* #ifndef strncasecmp */ +#endif /* _WIN32 */ +#if defined(__amigaos__) || defined(__amigaos4__) +#include +#endif /* defined(__amigaos__) || defined(__amigaos4__) */ +#if defined(__amigaos__) +#define uint16_t unsigned short +#endif /* defined(__amigaos__) */ +/* Hack */ +#define UNIX_PATH_LEN 108 +struct sockaddr_un { + uint16_t sun_family; + char sun_path[UNIX_PATH_LEN]; +}; +#else /* defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__) */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define closesocket close +#endif + +#ifdef _WIN32 +#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); +#else +#define PRINT_SOCKET_ERROR(x) perror(x) +#endif + +#if !defined(__DragonFly__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__APPLE__) && !defined(_WIN32) && !defined(__CYGWIN__) && !defined(__sun) && !defined(__GNU__) && !defined(__FreeBSD_kernel__) +#define HAS_IP_MREQN +#endif + +#if !defined(HAS_IP_MREQN) && !defined(_WIN32) +#include +#if defined(__sun) +#include +#endif +#endif + +#if defined(HAS_IP_MREQN) && defined(NEED_STRUCT_IP_MREQN) +/* Several versions of glibc don't define this structure, + * define it here and compile with CFLAGS NEED_STRUCT_IP_MREQN */ +struct ip_mreqn +{ + struct in_addr imr_multiaddr; /* IP multicast address of group */ + struct in_addr imr_address; /* local IP address of interface */ + int imr_ifindex; /* Interface index */ +}; +#endif + +#if defined(__amigaos__) || defined(__amigaos4__) +/* Amiga OS specific stuff */ +#define TIMEVAL struct timeval +#endif + +#include "minissdpc.h" +#include "miniupnpc.h" +#include "receivedata.h" + +#if !(defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__)) + +#include "codelength.h" + +struct UPNPDev * +getDevicesFromMiniSSDPD(const char * devtype, const char * socketpath, int * error) +{ + struct UPNPDev * devlist = NULL; + int s; + int res; + + s = connectToMiniSSDPD(socketpath); + if (s < 0) { + if (error) + *error = s; + return NULL; + } + res = requestDevicesFromMiniSSDPD(s, devtype); + if (res < 0) { + if (error) + *error = res; + } else { + devlist = receiveDevicesFromMiniSSDPD(s, error); + } + disconnectFromMiniSSDPD(s); + return devlist; +} + +/* macros used to read from unix socket */ +#define READ_BYTE_BUFFER(c) \ + if((int)bufferindex >= n) { \ + n = read(s, buffer, sizeof(buffer)); \ + if(n<=0) break; \ + bufferindex = 0; \ + } \ + c = buffer[bufferindex++]; + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ + +#define READ_COPY_BUFFER(dst, len) \ + for(l = len, p = (unsigned char *)dst; l > 0; ) { \ + unsigned int lcopy; \ + if((int)bufferindex >= n) { \ + n = read(s, buffer, sizeof(buffer)); \ + if(n<=0) break; \ + bufferindex = 0; \ + } \ + lcopy = MIN(l, (n - bufferindex)); \ + memcpy(p, buffer + bufferindex, lcopy); \ + l -= lcopy; \ + p += lcopy; \ + bufferindex += lcopy; \ + } + +#define READ_DISCARD_BUFFER(len) \ + for(l = len; l > 0; ) { \ + unsigned int lcopy; \ + if(bufferindex >= n) { \ + n = read(s, buffer, sizeof(buffer)); \ + if(n<=0) break; \ + bufferindex = 0; \ + } \ + lcopy = MIN(l, (n - bufferindex)); \ + l -= lcopy; \ + bufferindex += lcopy; \ + } + +int +connectToMiniSSDPD(const char * socketpath) +{ + int s; + struct sockaddr_un addr; +#if defined(MINIUPNPC_SET_SOCKET_TIMEOUT) && !defined(__sun) + struct timeval timeout; +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + + s = socket(AF_UNIX, SOCK_STREAM, 0); + if(s < 0) + { + /*syslog(LOG_ERR, "socket(unix): %m");*/ + perror("socket(unix)"); + return MINISSDPC_SOCKET_ERROR; + } +#if defined(MINIUPNPC_SET_SOCKET_TIMEOUT) && !defined(__sun) + /* setting a 3 seconds timeout */ + /* not supported for AF_UNIX sockets under Solaris */ + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + perror("setsockopt SO_RCVTIMEO unix"); + } + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + perror("setsockopt SO_SNDTIMEO unix"); + } +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + if(!socketpath) + socketpath = "/var/run/minissdpd.sock"; + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socketpath, sizeof(addr.sun_path)); + /* TODO : check if we need to handle the EINTR */ + if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) + { + /*syslog(LOG_WARNING, "connect(\"%s\"): %m", socketpath);*/ + close(s); + return MINISSDPC_SOCKET_ERROR; + } + return s; +} + +int +disconnectFromMiniSSDPD(int s) +{ + if (close(s) < 0) + return MINISSDPC_SOCKET_ERROR; + return MINISSDPC_SUCCESS; +} + +int +requestDevicesFromMiniSSDPD(int s, const char * devtype) +{ + unsigned char buffer[256]; + unsigned char * p; + unsigned int stsize, l; + + stsize = strlen(devtype); + if(stsize == 8 && 0 == memcmp(devtype, "ssdp:all", 8)) + { + buffer[0] = 3; /* request type 3 : everything */ + } + else + { + buffer[0] = 1; /* request type 1 : request devices/services by type */ + } + p = buffer + 1; + l = stsize; CODELENGTH(l, p); + if(p + stsize > buffer + sizeof(buffer)) + { + /* devtype is too long ! */ +#ifdef DEBUG + fprintf(stderr, "devtype is too long ! stsize=%u sizeof(buffer)=%u\n", + stsize, (unsigned)sizeof(buffer)); +#endif /* DEBUG */ + return MINISSDPC_INVALID_INPUT; + } + memcpy(p, devtype, stsize); + p += stsize; + if(write(s, buffer, p - buffer) < 0) + { + /*syslog(LOG_ERR, "write(): %m");*/ + perror("minissdpc.c: write()"); + return MINISSDPC_SOCKET_ERROR; + } + return MINISSDPC_SUCCESS; +} + +struct UPNPDev * +receiveDevicesFromMiniSSDPD(int s, int * error) +{ + struct UPNPDev * tmp; + struct UPNPDev * devlist = NULL; + unsigned char buffer[256]; + ssize_t n; + unsigned char * p; + unsigned char * url; + unsigned char * st; + unsigned int bufferindex; + unsigned int i, ndev; + unsigned int urlsize, stsize, usnsize, l; + + n = read(s, buffer, sizeof(buffer)); + if(n<=0) + { + perror("minissdpc.c: read()"); + if (error) + *error = MINISSDPC_SOCKET_ERROR; + return NULL; + } + ndev = buffer[0]; + bufferindex = 1; + for(i = 0; i < ndev; i++) + { + DECODELENGTH_READ(urlsize, READ_BYTE_BUFFER); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + return devlist; + } +#ifdef DEBUG + printf(" urlsize=%u", urlsize); +#endif /* DEBUG */ + url = malloc(urlsize); + if(url == NULL) { + if (error) + *error = MINISSDPC_MEMORY_ERROR; + return devlist; + } + READ_COPY_BUFFER(url, urlsize); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_url_and_return; + } + DECODELENGTH_READ(stsize, READ_BYTE_BUFFER); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_url_and_return; + } +#ifdef DEBUG + printf(" stsize=%u", stsize); +#endif /* DEBUG */ + st = malloc(stsize); + if (st == NULL) { + if (error) + *error = MINISSDPC_MEMORY_ERROR; + goto free_url_and_return; + } + READ_COPY_BUFFER(st, stsize); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_url_and_st_and_return; + } + DECODELENGTH_READ(usnsize, READ_BYTE_BUFFER); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_url_and_st_and_return; + } +#ifdef DEBUG + printf(" usnsize=%u\n", usnsize); +#endif /* DEBUG */ + tmp = (struct UPNPDev *)malloc(sizeof(struct UPNPDev)+urlsize+stsize+usnsize); + if(tmp == NULL) { + if (error) + *error = MINISSDPC_MEMORY_ERROR; + goto free_url_and_st_and_return; + } + tmp->pNext = devlist; + tmp->descURL = tmp->buffer; + tmp->st = tmp->buffer + 1 + urlsize; + memcpy(tmp->buffer, url, urlsize); + tmp->buffer[urlsize] = '\0'; + memcpy(tmp->st, st, stsize); + tmp->buffer[urlsize+1+stsize] = '\0'; + free(url); + free(st); + url = NULL; + st = NULL; + tmp->usn = tmp->buffer + 1 + urlsize + 1 + stsize; + READ_COPY_BUFFER(tmp->usn, usnsize); + if(n<=0) { + if (error) + *error = MINISSDPC_INVALID_SERVER_REPLY; + goto free_tmp_and_return; + } + tmp->buffer[urlsize+1+stsize+1+usnsize] = '\0'; + tmp->scope_id = 0; /* default value. scope_id is not available with MiniSSDPd */ + devlist = tmp; + } + if (error) + *error = MINISSDPC_SUCCESS; + return devlist; + +free_url_and_st_and_return: + free(st); +free_url_and_return: + free(url); + return devlist; + +free_tmp_and_return: + free(tmp); + return devlist; +} + +#endif /* !(defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__)) */ + +/* parseMSEARCHReply() + * the last 4 arguments are filled during the parsing : + * - location/locationsize : "location:" field of the SSDP reply packet + * - st/stsize : "st:" field of the SSDP reply packet. + * The strings are NOT null terminated */ +static void +parseMSEARCHReply(const char * reply, int size, + const char * * location, int * locationsize, + const char * * st, int * stsize, + const char * * usn, int * usnsize) +{ + int a, b, i; + i = 0; + a = i; /* start of the line */ + b = 0; /* end of the "header" (position of the colon) */ + while(isin6_family = AF_INET6; + if(localport > 0 && localport < 65536) + p->sin6_port = htons((unsigned short)localport); + p->sin6_addr = in6addr_any; /* in6addr_any is not available with MinGW32 3.4.2 */ + } else { + struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_r; + p->sin_family = AF_INET; + if(localport > 0 && localport < 65536) + p->sin_port = htons((unsigned short)localport); + p->sin_addr.s_addr = INADDR_ANY; + } +#ifdef _WIN32 +/* This code could help us to use the right Network interface for + * SSDP multicast traffic */ +/* Get IP associated with the index given in the ip_forward struct + * in order to give this ip to setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF) */ + if(!ipv6 + && (GetBestRoute(inet_addr("223.255.255.255"), 0, &ip_forward) == NO_ERROR)) { + DWORD dwRetVal = 0; + PMIB_IPADDRTABLE pIPAddrTable; + DWORD dwSize = 0; +#ifdef DEBUG + IN_ADDR IPAddr; +#endif + int i; +#ifdef DEBUG + printf("ifIndex=%lu nextHop=%lx \n", ip_forward.dwForwardIfIndex, ip_forward.dwForwardNextHop); +#endif + pIPAddrTable = (MIB_IPADDRTABLE *) malloc(sizeof (MIB_IPADDRTABLE)); + if(pIPAddrTable) { + if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) { + free(pIPAddrTable); + pIPAddrTable = (MIB_IPADDRTABLE *) malloc(dwSize); + } + } + if(pIPAddrTable) { + dwRetVal = GetIpAddrTable( pIPAddrTable, &dwSize, 0 ); + if (dwRetVal == NO_ERROR) { +#ifdef DEBUG + printf("\tNum Entries: %ld\n", pIPAddrTable->dwNumEntries); +#endif + for (i=0; i < (int) pIPAddrTable->dwNumEntries; i++) { +#ifdef DEBUG + printf("\n\tInterface Index[%d]:\t%ld\n", i, pIPAddrTable->table[i].dwIndex); + IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwAddr; + printf("\tIP Address[%d]: \t%s\n", i, inet_ntoa(IPAddr) ); + IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwMask; + printf("\tSubnet Mask[%d]: \t%s\n", i, inet_ntoa(IPAddr) ); + IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwBCastAddr; + printf("\tBroadCast[%d]: \t%s (%ld)\n", i, inet_ntoa(IPAddr), pIPAddrTable->table[i].dwBCastAddr); + printf("\tReassembly size[%d]:\t%ld\n", i, pIPAddrTable->table[i].dwReasmSize); + printf("\tType and State[%d]:", i); + printf("\n"); +#endif + if (pIPAddrTable->table[i].dwIndex == ip_forward.dwForwardIfIndex) { + /* Set the address of this interface to be used */ + struct in_addr mc_if; + memset(&mc_if, 0, sizeof(mc_if)); + mc_if.s_addr = pIPAddrTable->table[i].dwAddr; + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) { + PRINT_SOCKET_ERROR("setsockopt"); + } + ((struct sockaddr_in *)&sockudp_r)->sin_addr.s_addr = pIPAddrTable->table[i].dwAddr; +#ifndef DEBUG + break; +#endif + } + } + } + free(pIPAddrTable); + pIPAddrTable = NULL; + } + } +#endif /* _WIN32 */ + +#ifdef _WIN32 + if (setsockopt(sudp, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof (opt)) < 0) +#else + if (setsockopt(sudp, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)) < 0) +#endif + { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + PRINT_SOCKET_ERROR("setsockopt(SO_REUSEADDR,...)"); + return NULL; + } + +#ifdef _WIN32 + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_TTL, (const char *)&_ttl, sizeof(_ttl)) < 0) +#else /* _WIN32 */ + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) +#endif /* _WIN32 */ + { + /* not a fatal error */ + PRINT_SOCKET_ERROR("setsockopt(IP_MULTICAST_TTL,...)"); + } + + if(multicastif) + { + if(ipv6) { +#if !defined(_WIN32) + /* according to MSDN, if_nametoindex() is supported since + * MS Windows Vista and MS Windows Server 2008. + * http://msdn.microsoft.com/en-us/library/bb408409%28v=vs.85%29.aspx */ + unsigned int ifindex = if_nametoindex(multicastif); /* eth0, etc. */ + if(setsockopt(sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt IPV6_MULTICAST_IF"); + } +#else +#ifdef DEBUG + printf("Setting of multicast interface not supported in IPv6 under Windows.\n"); +#endif +#endif + } else { + struct in_addr mc_if; + mc_if.s_addr = inet_addr(multicastif); /* ex: 192.168.x.x */ + if(mc_if.s_addr != INADDR_NONE) + { + ((struct sockaddr_in *)&sockudp_r)->sin_addr.s_addr = mc_if.s_addr; + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); + } + } else { +#ifdef HAS_IP_MREQN + /* was not an ip address, try with an interface name */ + struct ip_mreqn reqn; /* only defined with -D_BSD_SOURCE or -D_GNU_SOURCE */ + memset(&reqn, 0, sizeof(struct ip_mreqn)); + reqn.imr_ifindex = if_nametoindex(multicastif); + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&reqn, sizeof(reqn)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); + } +#elif !defined(_WIN32) + struct ifreq ifr; + int ifrlen = sizeof(ifr); + strncpy(ifr.ifr_name, multicastif, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ-1] = '\0'; + if(ioctl(sudp, SIOCGIFADDR, &ifr, &ifrlen) < 0) + { + PRINT_SOCKET_ERROR("ioctl(...SIOCGIFADDR...)"); + } + mc_if.s_addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr; + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); + } +#else /* _WIN32 */ +#ifdef DEBUG + printf("Setting of multicast interface not supported with interface name.\n"); +#endif +#endif /* #ifdef HAS_IP_MREQN / !defined(_WIN32) */ + } + } + } + + /* Before sending the packed, we first "bind" in order to be able + * to receive the response */ + if (bind(sudp, (const struct sockaddr *)&sockudp_r, + ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) != 0) + { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + PRINT_SOCKET_ERROR("bind"); + closesocket(sudp); + return NULL; + } + + if(error) + *error = MINISSDPC_SUCCESS; + /* Calculating maximum response time in seconds */ + mx = ((unsigned int)delay) / 1000u; + if(mx == 0) { + mx = 1; + delay = 1000; + } + /* receiving SSDP response packet */ + for(deviceIndex = 0; deviceTypes[deviceIndex]; deviceIndex++) { + /* sending the SSDP M-SEARCH packet */ + n = snprintf(bufr, sizeof(bufr), + MSearchMsgFmt, + ipv6 ? + (linklocal ? "[" UPNP_MCAST_LL_ADDR "]" : "[" UPNP_MCAST_SL_ADDR "]") + : UPNP_MCAST_ADDR, + deviceTypes[deviceIndex], mx); + if ((unsigned int)n >= sizeof(bufr)) { + if(error) + *error = MINISSDPC_MEMORY_ERROR; + goto error; + } +#ifdef DEBUG + /*printf("Sending %s", bufr);*/ + printf("Sending M-SEARCH request to %s with ST: %s\n", + ipv6 ? + (linklocal ? "[" UPNP_MCAST_LL_ADDR "]" : "[" UPNP_MCAST_SL_ADDR "]") + : UPNP_MCAST_ADDR, + deviceTypes[deviceIndex]); +#endif +#ifdef NO_GETADDRINFO + /* the following code is not using getaddrinfo */ + /* emission */ + memset(&sockudp_w, 0, sizeof(struct sockaddr_storage)); + if(ipv6) { + struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockudp_w; + p->sin6_family = AF_INET6; + p->sin6_port = htons(SSDP_PORT); + inet_pton(AF_INET6, + linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR, + &(p->sin6_addr)); + } else { + struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_w; + p->sin_family = AF_INET; + p->sin_port = htons(SSDP_PORT); + p->sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR); + } + n = sendto(sudp, bufr, n, 0, &sockudp_w, + ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + if (n < 0) { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + PRINT_SOCKET_ERROR("sendto"); + break; + } +#else /* #ifdef NO_GETADDRINFO */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* AF_INET6 or AF_INET */ + hints.ai_socktype = SOCK_DGRAM; + /*hints.ai_flags = */ + if ((rv = getaddrinfo(ipv6 + ? (linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR) + : UPNP_MCAST_ADDR, + XSTR(SSDP_PORT), &hints, &servinfo)) != 0) { + if(error) + *error = MINISSDPC_SOCKET_ERROR; +#ifdef _WIN32 + fprintf(stderr, "getaddrinfo() failed: %d\n", rv); +#else + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); +#endif + break; + } + for(p = servinfo; p; p = p->ai_next) { + n = sendto(sudp, bufr, n, 0, p->ai_addr, p->ai_addrlen); + if (n < 0) { +#ifdef DEBUG + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + if (getnameinfo(p->ai_addr, p->ai_addrlen, hbuf, sizeof(hbuf), sbuf, + sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) == 0) { + fprintf(stderr, "host:%s port:%s\n", hbuf, sbuf); + } +#endif + PRINT_SOCKET_ERROR("sendto"); + continue; + } + } + freeaddrinfo(servinfo); + if(n < 0) { + if(error) + *error = MINISSDPC_SOCKET_ERROR; + break; + } +#endif /* #ifdef NO_GETADDRINFO */ + /* Waiting for SSDP REPLY packet to M-SEARCH + * if searchalltypes is set, enter the loop only + * when the last deviceType is reached */ + if(!searchalltypes || !deviceTypes[deviceIndex + 1]) do { + n = receivedata(sudp, bufr, sizeof(bufr), delay, &scope_id); + if (n < 0) { + /* error */ + if(error) + *error = MINISSDPC_SOCKET_ERROR; + goto error; + } else if (n == 0) { + /* no data or Time Out */ +#ifdef DEBUG + printf("NODATA or TIMEOUT\n"); +#endif /* DEBUG */ + if (devlist && !searchalltypes) { + /* found some devices, stop now*/ + if(error) + *error = MINISSDPC_SUCCESS; + goto error; + } + } else { + const char * descURL=NULL; + int urlsize=0; + const char * st=NULL; + int stsize=0; + const char * usn=NULL; + int usnsize=0; + parseMSEARCHReply(bufr, n, &descURL, &urlsize, &st, &stsize, &usn, &usnsize); + if(st&&descURL) { +#ifdef DEBUG + printf("M-SEARCH Reply:\n ST: %.*s\n USN: %.*s\n Location: %.*s\n", + stsize, st, usnsize, (usn?usn:""), urlsize, descURL); +#endif /* DEBUG */ + for(tmp=devlist; tmp; tmp = tmp->pNext) { + if(memcmp(tmp->descURL, descURL, urlsize) == 0 && + tmp->descURL[urlsize] == '\0' && + memcmp(tmp->st, st, stsize) == 0 && + tmp->st[stsize] == '\0' && + (usnsize == 0 || memcmp(tmp->usn, usn, usnsize) == 0) && + tmp->usn[usnsize] == '\0') + break; + } + /* at the exit of the loop above, tmp is null if + * no duplicate device was found */ + if(tmp) + continue; + tmp = (struct UPNPDev *)malloc(sizeof(struct UPNPDev)+urlsize+stsize+usnsize); + if(!tmp) { + /* memory allocation error */ + if(error) + *error = MINISSDPC_MEMORY_ERROR; + goto error; + } + tmp->pNext = devlist; + tmp->descURL = tmp->buffer; + tmp->st = tmp->buffer + 1 + urlsize; + tmp->usn = tmp->st + 1 + stsize; + memcpy(tmp->buffer, descURL, urlsize); + tmp->buffer[urlsize] = '\0'; + memcpy(tmp->st, st, stsize); + tmp->buffer[urlsize+1+stsize] = '\0'; + if(usn != NULL) + memcpy(tmp->usn, usn, usnsize); + tmp->buffer[urlsize+1+stsize+1+usnsize] = '\0'; + tmp->scope_id = scope_id; + devlist = tmp; + } + } + } while(n > 0); + if(ipv6) { + /* switch linklocal flag */ + if(linklocal) { + linklocal = 0; + --deviceIndex; + } else { + linklocal = 1; + } + } + } +error: + closesocket(sudp); + return devlist; +} + diff --git a/ext/miniupnpc/minissdpc.h b/ext/miniupnpc/minissdpc.h new file mode 100644 index 0000000..a5c622b --- /dev/null +++ b/ext/miniupnpc/minissdpc.h @@ -0,0 +1,58 @@ +/* $Id: minissdpc.h,v 1.7 2015/10/08 16:15:47 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author: Thomas Bernard + * Copyright (c) 2005-2015 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef MINISSDPC_H_INCLUDED +#define MINISSDPC_H_INCLUDED + +#include "miniupnpc_declspec.h" +#include "upnpdev.h" + +/* error codes : */ +#define MINISSDPC_SUCCESS (0) +#define MINISSDPC_UNKNOWN_ERROR (-1) +#define MINISSDPC_SOCKET_ERROR (-101) +#define MINISSDPC_MEMORY_ERROR (-102) +#define MINISSDPC_INVALID_INPUT (-103) +#define MINISSDPC_INVALID_SERVER_REPLY (-104) + +#ifdef __cplusplus +extern "C" { +#endif + +#if !(defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__)) + +MINIUPNP_LIBSPEC struct UPNPDev * +getDevicesFromMiniSSDPD(const char * devtype, const char * socketpath, int * error); + +MINIUPNP_LIBSPEC int +connectToMiniSSDPD(const char * socketpath); + +MINIUPNP_LIBSPEC int +disconnectFromMiniSSDPD(int fd); + +MINIUPNP_LIBSPEC int +requestDevicesFromMiniSSDPD(int fd, const char * devtype); + +MINIUPNP_LIBSPEC struct UPNPDev * +receiveDevicesFromMiniSSDPD(int fd, int * error); + +#endif /* !(defined(_WIN32) || defined(__amigaos__) || defined(__amigaos4__)) */ + +MINIUPNP_LIBSPEC struct UPNPDev * +ssdpDiscoverDevices(const char * const deviceTypes[], + int delay, const char * multicastif, + int localport, + int ipv6, unsigned char ttl, + int * error, + int searchalltypes); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/ext/miniupnpc/miniupnpc.c b/ext/miniupnpc/miniupnpc.c new file mode 100644 index 0000000..2dc5c95 --- /dev/null +++ b/ext/miniupnpc/miniupnpc.c @@ -0,0 +1,722 @@ +/* $Id: miniupnpc.c,v 1.149 2016/02/09 09:50:46 nanard Exp $ */ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp + * Web : http://miniupnp.free.fr/ + * Author : Thomas BERNARD + * copyright (c) 2005-2016 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENSE file. */ +#include +#include +#include +#ifdef _WIN32 +/* Win32 Specific includes and defines */ +#include +#include +#include +#include +#define snprintf _snprintf +#define strdup _strdup +#ifndef strncasecmp +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#define strncasecmp _memicmp +#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#define strncasecmp memicmp +#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#endif /* #ifndef strncasecmp */ +#define MAXHOSTNAMELEN 64 +#else /* #ifdef _WIN32 */ +/* Standard POSIX includes */ +#include +#if defined(__amigaos__) && !defined(__amigaos4__) +/* Amiga OS 3 specific stuff */ +#define socklen_t int +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#if !defined(__amigaos__) && !defined(__amigaos4__) +#include +#endif +#include +#include +#define closesocket close +#endif /* #else _WIN32 */ +#ifdef __GNU__ +#define MAXHOSTNAMELEN 64 +#endif + + +#include "miniupnpc.h" +#include "minissdpc.h" +#include "miniwget.h" +#include "minisoap.h" +#include "minixml.h" +#include "upnpcommands.h" +#include "connecthostport.h" + +/* compare the begining of a string with a constant string */ +#define COMPARE(str, cstr) (0==memcmp(str, cstr, sizeof(cstr) - 1)) + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +#define SOAPPREFIX "s" +#define SERVICEPREFIX "u" +#define SERVICEPREFIX2 'u' + +/* check if an ip address is a private (LAN) address + * see https://tools.ietf.org/html/rfc1918 */ +static int is_rfc1918addr(const char * addr) +{ + /* 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) */ + if(COMPARE(addr, "192.168.")) + return 1; + /* 10.0.0.0 - 10.255.255.255 (10/8 prefix) */ + if(COMPARE(addr, "10.")) + return 1; + /* 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) */ + if(COMPARE(addr, "172.")) { + int i = atoi(addr + 4); + if((16 <= i) && (i <= 31)) + return 1; + } + return 0; +} + +/* root description parsing */ +MINIUPNP_LIBSPEC void parserootdesc(const char * buffer, int bufsize, struct IGDdatas * data) +{ + struct xmlparser parser; + /* xmlparser object */ + parser.xmlstart = buffer; + parser.xmlsize = bufsize; + parser.data = data; + parser.starteltfunc = IGDstartelt; + parser.endeltfunc = IGDendelt; + parser.datafunc = IGDdata; + parser.attfunc = 0; + parsexml(&parser); +#ifdef DEBUG + printIGD(data); +#endif +} + +/* simpleUPnPcommand2 : + * not so simple ! + * return values : + * pointer - OK + * NULL - error */ +char * simpleUPnPcommand2(int s, const char * url, const char * service, + const char * action, struct UPNParg * args, + int * bufsize, const char * httpversion) +{ + char hostname[MAXHOSTNAMELEN+1]; + unsigned short port = 0; + char * path; + char soapact[128]; + char soapbody[2048]; + int soapbodylen; + char * buf; + int n; + int status_code; + + *bufsize = 0; + snprintf(soapact, sizeof(soapact), "%s#%s", service, action); + if(args==NULL) + { + soapbodylen = snprintf(soapbody, sizeof(soapbody), + "\r\n" + "<" SOAPPREFIX ":Envelope " + "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " + SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<" SOAPPREFIX ":Body>" + "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">" + "" + "" + "\r\n", action, service, action); + if ((unsigned int)soapbodylen >= sizeof(soapbody)) + return NULL; + } + else + { + char * p; + const char * pe, * pv; + const char * const pend = soapbody + sizeof(soapbody); + soapbodylen = snprintf(soapbody, sizeof(soapbody), + "\r\n" + "<" SOAPPREFIX ":Envelope " + "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " + SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<" SOAPPREFIX ":Body>" + "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">", + action, service); + if ((unsigned int)soapbodylen >= sizeof(soapbody)) + return NULL; + p = soapbody + soapbodylen; + while(args->elt) + { + if(p >= pend) /* check for space to write next byte */ + return NULL; + *(p++) = '<'; + + pe = args->elt; + while(p < pend && *pe) + *(p++) = *(pe++); + + if(p >= pend) /* check for space to write next byte */ + return NULL; + *(p++) = '>'; + + if((pv = args->val)) + { + while(p < pend && *pv) + *(p++) = *(pv++); + } + + if((p+2) > pend) /* check for space to write next 2 bytes */ + return NULL; + *(p++) = '<'; + *(p++) = '/'; + + pe = args->elt; + while(p < pend && *pe) + *(p++) = *(pe++); + + if(p >= pend) /* check for space to write next byte */ + return NULL; + *(p++) = '>'; + + args++; + } + if((p+4) > pend) /* check for space to write next 4 bytes */ + return NULL; + *(p++) = '<'; + *(p++) = '/'; + *(p++) = SERVICEPREFIX2; + *(p++) = ':'; + + pe = action; + while(p < pend && *pe) + *(p++) = *(pe++); + + strncpy(p, ">\r\n", + pend - p); + if(soapbody[sizeof(soapbody)-1]) /* strncpy pads buffer with 0s, so if it doesn't end in 0, could not fit full string */ + return NULL; + } + if(!parseURL(url, hostname, &port, &path, NULL)) return NULL; + if(s < 0) { + s = connecthostport(hostname, port, 0); + if(s < 0) { + /* failed to connect */ + return NULL; + } + } + + n = soapPostSubmit(s, path, hostname, port, soapact, soapbody, httpversion); + if(n<=0) { +#ifdef DEBUG + printf("Error sending SOAP request\n"); +#endif + closesocket(s); + return NULL; + } + + buf = getHTTPResponse(s, bufsize, &status_code); +#ifdef DEBUG + if(*bufsize > 0 && buf) + { + printf("HTTP %d SOAP Response :\n%.*s\n", status_code, *bufsize, buf); + } + else + { + printf("HTTP %d, empty SOAP response. size=%d\n", status_code, *bufsize); + } +#endif + closesocket(s); + return buf; +} + +/* simpleUPnPcommand : + * not so simple ! + * return values : + * pointer - OK + * NULL - error */ +char * simpleUPnPcommand(int s, const char * url, const char * service, + const char * action, struct UPNParg * args, + int * bufsize) +{ + char * buf; + +#if 1 + buf = simpleUPnPcommand2(s, url, service, action, args, bufsize, "1.1"); +#else + buf = simpleUPnPcommand2(s, url, service, action, args, bufsize, "1.0"); + if (!buf || *bufsize == 0) + { +#if DEBUG + printf("Error or no result from SOAP request; retrying with HTTP/1.1\n"); +#endif + buf = simpleUPnPcommand2(s, url, service, action, args, bufsize, "1.1"); + } +#endif + return buf; +} + +/* upnpDiscoverDevices() : + * return a chained list of all devices found or NULL if + * no devices was found. + * It is up to the caller to free the chained list + * delay is in millisecond (poll). + * UDA v1.1 says : + * The TTL for the IP packet SHOULD default to 2 and + * SHOULD be configurable. */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevices(const char * const deviceTypes[], + int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error, + int searchalltypes) +{ + struct UPNPDev * tmp; + struct UPNPDev * devlist = 0; +#if !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) + int deviceIndex; +#endif /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ + + if(error) + *error = UPNPDISCOVER_UNKNOWN_ERROR; +#if !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) + /* first try to get infos from minissdpd ! */ + if(!minissdpdsock) + minissdpdsock = "/var/run/minissdpd.sock"; + for(deviceIndex = 0; deviceTypes[deviceIndex]; deviceIndex++) { + struct UPNPDev * minissdpd_devlist; + int only_rootdevice = 1; + minissdpd_devlist = getDevicesFromMiniSSDPD(deviceTypes[deviceIndex], + minissdpdsock, 0); + if(minissdpd_devlist) { +#ifdef DEBUG + printf("returned by MiniSSDPD: %s\t%s\n", + minissdpd_devlist->st, minissdpd_devlist->descURL); +#endif /* DEBUG */ + if(!strstr(minissdpd_devlist->st, "rootdevice")) + only_rootdevice = 0; + for(tmp = minissdpd_devlist; tmp->pNext != NULL; tmp = tmp->pNext) { +#ifdef DEBUG + printf("returned by MiniSSDPD: %s\t%s\n", + tmp->pNext->st, tmp->pNext->descURL); +#endif /* DEBUG */ + if(!strstr(tmp->st, "rootdevice")) + only_rootdevice = 0; + } + tmp->pNext = devlist; + devlist = minissdpd_devlist; + if(!searchalltypes && !only_rootdevice) + break; + } + } + for(tmp = devlist; tmp != NULL; tmp = tmp->pNext) { + /* We return what we have found if it was not only a rootdevice */ + if(!strstr(tmp->st, "rootdevice")) { + if(error) + *error = UPNPDISCOVER_SUCCESS; + return devlist; + } + } +#endif /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ + + /* direct discovery if minissdpd responses are not sufficient */ + { + struct UPNPDev * discovered_devlist; + discovered_devlist = ssdpDiscoverDevices(deviceTypes, delay, multicastif, localport, + ipv6, ttl, error, searchalltypes); + if(devlist == NULL) + devlist = discovered_devlist; + else { + for(tmp = devlist; tmp->pNext != NULL; tmp = tmp->pNext); + tmp->pNext = discovered_devlist; + } + } + return devlist; +} + +/* upnpDiscover() Discover IGD device */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error) +{ + static const char * const deviceList[] = { +#if 0 + "urn:schemas-upnp-org:device:InternetGatewayDevice:2", + "urn:schemas-upnp-org:service:WANIPConnection:2", +#endif + "urn:schemas-upnp-org:device:InternetGatewayDevice:1", + "urn:schemas-upnp-org:service:WANIPConnection:1", + "urn:schemas-upnp-org:service:WANPPPConnection:1", + "upnp:rootdevice", + /*"ssdp:all",*/ + 0 + }; + return upnpDiscoverDevices(deviceList, + delay, multicastif, minissdpdsock, localport, + ipv6, ttl, error, 0); +} + +/* upnpDiscoverAll() Discover all UPnP devices */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverAll(int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error) +{ + static const char * const deviceList[] = { + /*"upnp:rootdevice",*/ + "ssdp:all", + 0 + }; + return upnpDiscoverDevices(deviceList, + delay, multicastif, minissdpdsock, localport, + ipv6, ttl, error, 0); +} + +/* upnpDiscoverDevice() Discover a specific device */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevice(const char * device, int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error) +{ + const char * const deviceList[] = { + device, + 0 + }; + return upnpDiscoverDevices(deviceList, + delay, multicastif, minissdpdsock, localport, + ipv6, ttl, error, 0); +} + +static char * +build_absolute_url(const char * baseurl, const char * descURL, + const char * url, unsigned int scope_id) +{ + int l, n; + char * s; + const char * base; + char * p; +#if defined(IF_NAMESIZE) && !defined(_WIN32) + char ifname[IF_NAMESIZE]; +#else /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + char scope_str[8]; +#endif /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + + if( (url[0] == 'h') + &&(url[1] == 't') + &&(url[2] == 't') + &&(url[3] == 'p') + &&(url[4] == ':') + &&(url[5] == '/') + &&(url[6] == '/')) + return strdup(url); + base = (baseurl[0] == '\0') ? descURL : baseurl; + n = strlen(base); + if(n > 7) { + p = strchr(base + 7, '/'); + if(p) + n = p - base; + } + l = n + strlen(url) + 1; + if(url[0] != '/') + l++; + if(scope_id != 0) { +#if defined(IF_NAMESIZE) && !defined(_WIN32) + if(if_indextoname(scope_id, ifname)) { + l += 3 + strlen(ifname); /* 3 == strlen(%25) */ + } +#else /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + /* under windows, scope is numerical */ + l += 3 + snprintf(scope_str, sizeof(scope_str), "%u", scope_id); +#endif /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + } + s = malloc(l); + if(s == NULL) return NULL; + memcpy(s, base, n); + if(scope_id != 0) { + s[n] = '\0'; + if(0 == memcmp(s, "http://[fe80:", 13)) { + /* this is a linklocal IPv6 address */ + p = strchr(s, ']'); + if(p) { + /* insert %25 into URL */ +#if defined(IF_NAMESIZE) && !defined(_WIN32) + memmove(p + 3 + strlen(ifname), p, strlen(p) + 1); + memcpy(p, "%25", 3); + memcpy(p + 3, ifname, strlen(ifname)); + n += 3 + strlen(ifname); +#else /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + memmove(p + 3 + strlen(scope_str), p, strlen(p) + 1); + memcpy(p, "%25", 3); + memcpy(p + 3, scope_str, strlen(scope_str)); + n += 3 + strlen(scope_str); +#endif /* defined(IF_NAMESIZE) && !defined(_WIN32) */ + } + } + } + if(url[0] != '/') + s[n++] = '/'; + memcpy(s + n, url, l - n); + return s; +} + +/* Prepare the Urls for usage... + */ +MINIUPNP_LIBSPEC void +GetUPNPUrls(struct UPNPUrls * urls, struct IGDdatas * data, + const char * descURL, unsigned int scope_id) +{ + /* strdup descURL */ + urls->rootdescURL = strdup(descURL); + + /* get description of WANIPConnection */ + urls->ipcondescURL = build_absolute_url(data->urlbase, descURL, + data->first.scpdurl, scope_id); + urls->controlURL = build_absolute_url(data->urlbase, descURL, + data->first.controlurl, scope_id); + urls->controlURL_CIF = build_absolute_url(data->urlbase, descURL, + data->CIF.controlurl, scope_id); + urls->controlURL_6FC = build_absolute_url(data->urlbase, descURL, + data->IPv6FC.controlurl, scope_id); + +#ifdef DEBUG + printf("urls->ipcondescURL='%s'\n", urls->ipcondescURL); + printf("urls->controlURL='%s'\n", urls->controlURL); + printf("urls->controlURL_CIF='%s'\n", urls->controlURL_CIF); + printf("urls->controlURL_6FC='%s'\n", urls->controlURL_6FC); +#endif +} + +MINIUPNP_LIBSPEC void +FreeUPNPUrls(struct UPNPUrls * urls) +{ + if(!urls) + return; + free(urls->controlURL); + urls->controlURL = 0; + free(urls->ipcondescURL); + urls->ipcondescURL = 0; + free(urls->controlURL_CIF); + urls->controlURL_CIF = 0; + free(urls->controlURL_6FC); + urls->controlURL_6FC = 0; + free(urls->rootdescURL); + urls->rootdescURL = 0; +} + +int +UPNPIGD_IsConnected(struct UPNPUrls * urls, struct IGDdatas * data) +{ + char status[64]; + unsigned int uptime; + status[0] = '\0'; + UPNP_GetStatusInfo(urls->controlURL, data->first.servicetype, + status, &uptime, NULL); + if(0 == strcmp("Connected", status)) + return 1; + else if(0 == strcmp("Up", status)) /* Also accept "Up" */ + return 1; + else + return 0; +} + + +/* UPNP_GetValidIGD() : + * return values : + * -1 = Internal error + * 0 = NO IGD found + * 1 = A valid connected IGD has been found + * 2 = A valid IGD has been found but it reported as + * not connected + * 3 = an UPnP device has been found but was not recognized as an IGD + * + * In any positive non zero return case, the urls and data structures + * passed as parameters are set. Dont forget to call FreeUPNPUrls(urls) to + * free allocated memory. + */ +MINIUPNP_LIBSPEC int +UPNP_GetValidIGD(struct UPNPDev * devlist, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen) +{ + struct xml_desc { + char * xml; + int size; + int is_igd; + } * desc = NULL; + struct UPNPDev * dev; + int ndev = 0; + int i; + int state = -1; /* state 1 : IGD connected. State 2 : IGD. State 3 : anything */ + int n_igd = 0; + char extIpAddr[16]; + char myLanAddr[40]; + int status_code = -1; + + if(!devlist) + { +#ifdef DEBUG + printf("Empty devlist\n"); +#endif + return 0; + } + /* counting total number of devices in the list */ + for(dev = devlist; dev; dev = dev->pNext) + ndev++; + if(ndev > 0) + { + desc = calloc(ndev, sizeof(struct xml_desc)); + if(!desc) + return -1; /* memory allocation error */ + } + /* Step 1 : downloading descriptions and testing type */ + for(dev = devlist, i = 0; dev; dev = dev->pNext, i++) + { + /* we should choose an internet gateway device. + * with st == urn:schemas-upnp-org:device:InternetGatewayDevice:1 */ + desc[i].xml = miniwget_getaddr(dev->descURL, &(desc[i].size), + myLanAddr, sizeof(myLanAddr), + dev->scope_id, &status_code); +#ifdef DEBUG + if(!desc[i].xml) + { + printf("error getting XML description %s\n", dev->descURL); + } +#endif + if(desc[i].xml) + { + memset(data, 0, sizeof(struct IGDdatas)); + memset(urls, 0, sizeof(struct UPNPUrls)); + parserootdesc(desc[i].xml, desc[i].size, data); + if(COMPARE(data->CIF.servicetype, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:")) + { + desc[i].is_igd = 1; + n_igd++; + if(lanaddr) + strncpy(lanaddr, myLanAddr, lanaddrlen); + } + } + } + /* iterate the list to find a device depending on state */ + for(state = 1; state <= 3; state++) + { + for(dev = devlist, i = 0; dev; dev = dev->pNext, i++) + { + if(desc[i].xml) + { + memset(data, 0, sizeof(struct IGDdatas)); + memset(urls, 0, sizeof(struct UPNPUrls)); + parserootdesc(desc[i].xml, desc[i].size, data); + if(desc[i].is_igd || state >= 3 ) + { + int is_connected; + + GetUPNPUrls(urls, data, dev->descURL, dev->scope_id); + + /* in state 2 and 3 we dont test if device is connected ! */ + if(state >= 2) + goto free_and_return; + is_connected = UPNPIGD_IsConnected(urls, data); +#ifdef DEBUG + printf("UPNPIGD_IsConnected(%s) = %d\n", + urls->controlURL, is_connected); +#endif + /* checks that status is connected AND there is a external IP address assigned */ + if(is_connected && + (UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, extIpAddr) == 0)) { + if(!is_rfc1918addr(extIpAddr) && (extIpAddr[0] != '\0') + && (0 != strcmp(extIpAddr, "0.0.0.0"))) + goto free_and_return; + } + FreeUPNPUrls(urls); + if(data->second.servicetype[0] != '\0') { +#ifdef DEBUG + printf("We tried %s, now we try %s !\n", + data->first.servicetype, data->second.servicetype); +#endif + /* swaping WANPPPConnection and WANIPConnection ! */ + memcpy(&data->tmp, &data->first, sizeof(struct IGDdatas_service)); + memcpy(&data->first, &data->second, sizeof(struct IGDdatas_service)); + memcpy(&data->second, &data->tmp, sizeof(struct IGDdatas_service)); + GetUPNPUrls(urls, data, dev->descURL, dev->scope_id); + is_connected = UPNPIGD_IsConnected(urls, data); +#ifdef DEBUG + printf("UPNPIGD_IsConnected(%s) = %d\n", + urls->controlURL, is_connected); +#endif + if(is_connected && + (UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, extIpAddr) == 0)) { + if(!is_rfc1918addr(extIpAddr) && (extIpAddr[0] != '\0') + && (0 != strcmp(extIpAddr, "0.0.0.0"))) + goto free_and_return; + } + FreeUPNPUrls(urls); + } + } + memset(data, 0, sizeof(struct IGDdatas)); + } + } + } + state = 0; +free_and_return: + if(desc) { + for(i = 0; i < ndev; i++) { + if(desc[i].xml) { + free(desc[i].xml); + } + } + free(desc); + } + return state; +} + +/* UPNP_GetIGDFromUrl() + * Used when skipping the discovery process. + * return value : + * 0 - Not ok + * 1 - OK */ +int +UPNP_GetIGDFromUrl(const char * rootdescurl, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen) +{ + char * descXML; + int descXMLsize = 0; + + descXML = miniwget_getaddr(rootdescurl, &descXMLsize, + lanaddr, lanaddrlen, 0, NULL); + if(descXML) { + memset(data, 0, sizeof(struct IGDdatas)); + memset(urls, 0, sizeof(struct UPNPUrls)); + parserootdesc(descXML, descXMLsize, data); + free(descXML); + descXML = NULL; + GetUPNPUrls(urls, data, rootdescurl, 0); + return 1; + } else { + return 0; + } +} + diff --git a/ext/miniupnpc/miniupnpc.def b/ext/miniupnpc/miniupnpc.def new file mode 100644 index 0000000..60e0bbe --- /dev/null +++ b/ext/miniupnpc/miniupnpc.def @@ -0,0 +1,45 @@ +LIBRARY +; miniupnpc library + miniupnpc + +EXPORTS +; miniupnpc + upnpDiscover + freeUPNPDevlist + parserootdesc + UPNP_GetValidIGD + UPNP_GetIGDFromUrl + GetUPNPUrls + FreeUPNPUrls +; miniwget + miniwget + miniwget_getaddr +; upnpcommands + UPNP_GetTotalBytesSent + UPNP_GetTotalBytesReceived + UPNP_GetTotalPacketsSent + UPNP_GetTotalPacketsReceived + UPNP_GetStatusInfo + UPNP_GetConnectionTypeInfo + UPNP_GetExternalIPAddress + UPNP_GetLinkLayerMaxBitRates + UPNP_AddPortMapping + UPNP_AddAnyPortMapping + UPNP_DeletePortMapping + UPNP_DeletePortMappingRange + UPNP_GetPortMappingNumberOfEntries + UPNP_GetSpecificPortMappingEntry + UPNP_GetGenericPortMappingEntry + UPNP_GetListOfPortMappings + UPNP_AddPinhole + UPNP_CheckPinholeWorking + UPNP_UpdatePinhole + UPNP_GetPinholePackets + UPNP_DeletePinhole + UPNP_GetFirewallStatus + UPNP_GetOutboundPinholeTimeout +; upnperrors + strupnperror +; portlistingparse + ParsePortListing + FreePortListing diff --git a/ext/miniupnpc/miniupnpc.h b/ext/miniupnpc/miniupnpc.h new file mode 100644 index 0000000..4cc45f7 --- /dev/null +++ b/ext/miniupnpc/miniupnpc.h @@ -0,0 +1,152 @@ +/* $Id: miniupnpc.h,v 1.50 2016/04/19 21:06:21 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ + * Author: Thomas Bernard + * Copyright (c) 2005-2016 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef MINIUPNPC_H_INCLUDED +#define MINIUPNPC_H_INCLUDED + +#include "miniupnpc_declspec.h" +#include "igd_desc_parse.h" +#include "upnpdev.h" + +/* error codes : */ +#define UPNPDISCOVER_SUCCESS (0) +#define UPNPDISCOVER_UNKNOWN_ERROR (-1) +#define UPNPDISCOVER_SOCKET_ERROR (-101) +#define UPNPDISCOVER_MEMORY_ERROR (-102) + +/* versions : */ +#define MINIUPNPC_VERSION "2.0.20161216" +#define MINIUPNPC_API_VERSION 16 + +/* Source port: + Using "1" as an alias for 1900 for backwards compatability + (presuming one would have used that for the "sameport" parameter) */ +#define UPNP_LOCAL_PORT_ANY 0 +#define UPNP_LOCAL_PORT_SAME 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* Structures definitions : */ +struct UPNParg { const char * elt; const char * val; }; + +char * +simpleUPnPcommand(int, const char *, const char *, + const char *, struct UPNParg *, + int *); + +/* upnpDiscover() + * discover UPnP devices on the network. + * The discovered devices are returned as a chained list. + * It is up to the caller to free the list with freeUPNPDevlist(). + * delay (in millisecond) is the maximum time for waiting any device + * response. + * If available, device list will be obtained from MiniSSDPd. + * Default path for minissdpd socket will be used if minissdpdsock argument + * is NULL. + * If multicastif is not NULL, it will be used instead of the default + * multicast interface for sending SSDP discover packets. + * If localport is set to UPNP_LOCAL_PORT_SAME(1) SSDP packets will be sent + * from the source port 1900 (same as destination port), if set to + * UPNP_LOCAL_PORT_ANY(0) system assign a source port, any other value will + * be attempted as the source port. + * "searchalltypes" parameter is useful when searching several types, + * if 0, the discovery will stop with the first type returning results. + * TTL should default to 2. */ +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error); + +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverAll(int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error); + +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevice(const char * device, int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error); + +MINIUPNP_LIBSPEC struct UPNPDev * +upnpDiscoverDevices(const char * const deviceTypes[], + int delay, const char * multicastif, + const char * minissdpdsock, int localport, + int ipv6, unsigned char ttl, + int * error, + int searchalltypes); + +/* parserootdesc() : + * parse root XML description of a UPnP device and fill the IGDdatas + * structure. */ +MINIUPNP_LIBSPEC void parserootdesc(const char *, int, struct IGDdatas *); + +/* structure used to get fast access to urls + * controlURL: controlURL of the WANIPConnection + * ipcondescURL: url of the description of the WANIPConnection + * controlURL_CIF: controlURL of the WANCommonInterfaceConfig + * controlURL_6FC: controlURL of the WANIPv6FirewallControl + */ +struct UPNPUrls { + char * controlURL; + char * ipcondescURL; + char * controlURL_CIF; + char * controlURL_6FC; + char * rootdescURL; +}; + +/* UPNP_GetValidIGD() : + * return values : + * 0 = NO IGD found + * 1 = A valid connected IGD has been found + * 2 = A valid IGD has been found but it reported as + * not connected + * 3 = an UPnP device has been found but was not recognized as an IGD + * + * In any non zero return case, the urls and data structures + * passed as parameters are set. Donc forget to call FreeUPNPUrls(urls) to + * free allocated memory. + */ +MINIUPNP_LIBSPEC int +UPNP_GetValidIGD(struct UPNPDev * devlist, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen); + +/* UPNP_GetIGDFromUrl() + * Used when skipping the discovery process. + * When succeding, urls, data, and lanaddr arguments are set. + * return value : + * 0 - Not ok + * 1 - OK */ +MINIUPNP_LIBSPEC int +UPNP_GetIGDFromUrl(const char * rootdescurl, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen); + +MINIUPNP_LIBSPEC void +GetUPNPUrls(struct UPNPUrls *, struct IGDdatas *, + const char *, unsigned int); + +MINIUPNP_LIBSPEC void +FreeUPNPUrls(struct UPNPUrls *); + +/* return 0 or 1 */ +MINIUPNP_LIBSPEC int UPNPIGD_IsConnected(struct UPNPUrls *, struct IGDdatas *); + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/ext/miniupnpc/miniupnpc_declspec.h b/ext/miniupnpc/miniupnpc_declspec.h new file mode 100644 index 0000000..40adb92 --- /dev/null +++ b/ext/miniupnpc/miniupnpc_declspec.h @@ -0,0 +1,21 @@ +#ifndef MINIUPNPC_DECLSPEC_H_INCLUDED +#define MINIUPNPC_DECLSPEC_H_INCLUDED + +#if defined(_WIN32) && !defined(MINIUPNP_STATICLIB) + /* for windows dll */ + #ifdef MINIUPNP_EXPORTS + #define MINIUPNP_LIBSPEC __declspec(dllexport) + #else + #define MINIUPNP_LIBSPEC __declspec(dllimport) + #endif +#else + #if defined(__GNUC__) && __GNUC__ >= 4 + /* fix dynlib for OS X 10.9.2 and Apple LLVM version 5.0 */ + #define MINIUPNP_LIBSPEC __attribute__ ((visibility ("default"))) + #else + #define MINIUPNP_LIBSPEC + #endif +#endif + +#endif /* MINIUPNPC_DECLSPEC_H_INCLUDED */ + diff --git a/ext/miniupnpc/miniupnpcmodule.c b/ext/miniupnpc/miniupnpcmodule.c new file mode 100644 index 0000000..a5bdce4 --- /dev/null +++ b/ext/miniupnpc/miniupnpcmodule.c @@ -0,0 +1,695 @@ +/* $Id: miniupnpcmodule.c,v 1.29 2015/10/26 17:01:30 nanard Exp $*/ +/* Project : miniupnp + * Author : Thomas BERNARD + * website : http://miniupnp.tuxfamily.org/ + * copyright (c) 2007-2014 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +#include +#define MINIUPNP_STATICLIB +#include "structmember.h" +#include "miniupnpc.h" +#include "upnpcommands.h" +#include "upnperrors.h" + +/* for compatibility with Python < 2.4 */ +#ifndef Py_RETURN_NONE +#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None +#endif + +#ifndef Py_RETURN_TRUE +#define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True +#endif + +#ifndef Py_RETURN_FALSE +#define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False +#endif + +/* for compatibility with Python < 3.0 */ +#ifndef PyVarObject_HEAD_INIT +#define PyVarObject_HEAD_INIT(type, size) \ + PyObject_HEAD_INIT(type) size, +#endif + +#ifndef Py_TYPE +#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) +#endif + +typedef struct { + PyObject_HEAD + /* Type-specific fields go here. */ + struct UPNPDev * devlist; + struct UPNPUrls urls; + struct IGDdatas data; + unsigned int discoverdelay; /* value passed to upnpDiscover() */ + unsigned int localport; /* value passed to upnpDiscover() */ + char lanaddr[40]; /* our ip address on the LAN */ + char * multicastif; + char * minissdpdsocket; +} UPnPObject; + +static PyMemberDef UPnP_members[] = { + {"lanaddr", T_STRING_INPLACE, offsetof(UPnPObject, lanaddr), + READONLY, "ip address on the LAN" + }, + {"discoverdelay", T_UINT, offsetof(UPnPObject, discoverdelay), + 0/*READWRITE*/, "value in ms used to wait for SSDP responses" + }, + {"localport", T_UINT, offsetof(UPnPObject, localport), + 0/*READWRITE*/, + "If localport is set to UPNP_LOCAL_PORT_SAME(1) " + "SSDP packets will be sent from the source port " + "1900 (same as destination port), if set to " + "UPNP_LOCAL_PORT_ANY(0) system assign a source " + "port, any other value will be attempted as the " + "source port" + }, + /* T_STRING is allways readonly :( */ + {"multicastif", T_STRING, offsetof(UPnPObject, multicastif), + 0, "IP of the network interface to be used for multicast operations" + }, + {"minissdpdsocket", T_STRING, offsetof(UPnPObject, minissdpdsocket), + 0, "path of the MiniSSDPd unix socket" + }, + {NULL} +}; + + +static int UPnP_init(UPnPObject *self, PyObject *args, PyObject *kwds) +{ + char* multicastif = NULL; + char* minissdpdsocket = NULL; + static char *kwlist[] = { + "multicastif", "minissdpdsocket", "discoverdelay", + "localport", NULL + }; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, "|zzII", kwlist, + &multicastif, + &minissdpdsocket, + &self->discoverdelay, + &self->localport)) + return -1; + + if(self->localport>1 && + (self->localport>65534||self->localport<1024)) { + PyErr_SetString(PyExc_Exception, "Invalid localport value"); + return -1; + } + if(multicastif) + self->multicastif = strdup(multicastif); + if(minissdpdsocket) + self->minissdpdsocket = strdup(minissdpdsocket); + + return 0; +} + +static void +UPnPObject_dealloc(UPnPObject *self) +{ + freeUPNPDevlist(self->devlist); + FreeUPNPUrls(&self->urls); + free(self->multicastif); + free(self->minissdpdsocket); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static PyObject * +UPnP_discover(UPnPObject *self) +{ + struct UPNPDev * dev; + int i; + PyObject *res = NULL; + if(self->devlist) + { + freeUPNPDevlist(self->devlist); + self->devlist = 0; + } + Py_BEGIN_ALLOW_THREADS + self->devlist = upnpDiscover((int)self->discoverdelay/*timeout in ms*/, + self->multicastif, + self->minissdpdsocket, + (int)self->localport, + 0/*ip v6*/, + 2/* TTL */, + 0/*error */); + Py_END_ALLOW_THREADS + /* Py_RETURN_NONE ??? */ + for(dev = self->devlist, i = 0; dev; dev = dev->pNext) + i++; + res = Py_BuildValue("i", i); + return res; +} + +static PyObject * +UPnP_selectigd(UPnPObject *self) +{ + int r; +Py_BEGIN_ALLOW_THREADS + r = UPNP_GetValidIGD(self->devlist, &self->urls, &self->data, + self->lanaddr, sizeof(self->lanaddr)); +Py_END_ALLOW_THREADS + if(r) + { + return Py_BuildValue("s", self->urls.controlURL); + } + else + { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, "No UPnP device discovered"); + return NULL; + } +} + +static PyObject * +UPnP_totalbytesent(UPnPObject *self) +{ + UNSIGNED_INTEGER i; +Py_BEGIN_ALLOW_THREADS + i = UPNP_GetTotalBytesSent(self->urls.controlURL_CIF, + self->data.CIF.servicetype); +Py_END_ALLOW_THREADS +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("I", i); +#else + return Py_BuildValue("i", (int)i); +#endif +} + +static PyObject * +UPnP_totalbytereceived(UPnPObject *self) +{ + UNSIGNED_INTEGER i; +Py_BEGIN_ALLOW_THREADS + i = UPNP_GetTotalBytesReceived(self->urls.controlURL_CIF, + self->data.CIF.servicetype); +Py_END_ALLOW_THREADS +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("I", i); +#else + return Py_BuildValue("i", (int)i); +#endif +} + +static PyObject * +UPnP_totalpacketsent(UPnPObject *self) +{ + UNSIGNED_INTEGER i; +Py_BEGIN_ALLOW_THREADS + i = UPNP_GetTotalPacketsSent(self->urls.controlURL_CIF, + self->data.CIF.servicetype); +Py_END_ALLOW_THREADS +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("I", i); +#else + return Py_BuildValue("i", (int)i); +#endif +} + +static PyObject * +UPnP_totalpacketreceived(UPnPObject *self) +{ + UNSIGNED_INTEGER i; +Py_BEGIN_ALLOW_THREADS + i = UPNP_GetTotalPacketsReceived(self->urls.controlURL_CIF, + self->data.CIF.servicetype); +Py_END_ALLOW_THREADS +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("I", i); +#else + return Py_BuildValue("i", (int)i); +#endif +} + +static PyObject * +UPnP_statusinfo(UPnPObject *self) +{ + char status[64]; + char lastconnerror[64]; + unsigned int uptime = 0; + int r; + status[0] = '\0'; + lastconnerror[0] = '\0'; +Py_BEGIN_ALLOW_THREADS + r = UPNP_GetStatusInfo(self->urls.controlURL, self->data.first.servicetype, + status, &uptime, lastconnerror); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("(s,I,s)", status, uptime, lastconnerror); +#else + return Py_BuildValue("(s,i,s)", status, (int)uptime, lastconnerror); +#endif + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +static PyObject * +UPnP_connectiontype(UPnPObject *self) +{ + char connectionType[64]; + int r; + connectionType[0] = '\0'; +Py_BEGIN_ALLOW_THREADS + r = UPNP_GetConnectionTypeInfo(self->urls.controlURL, + self->data.first.servicetype, + connectionType); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { + return Py_BuildValue("s", connectionType); + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +static PyObject * +UPnP_externalipaddress(UPnPObject *self) +{ + char externalIPAddress[40]; + int r; + externalIPAddress[0] = '\0'; +Py_BEGIN_ALLOW_THREADS + r = UPNP_GetExternalIPAddress(self->urls.controlURL, + self->data.first.servicetype, + externalIPAddress); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { + return Py_BuildValue("s", externalIPAddress); + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +/* AddPortMapping(externalPort, protocol, internalHost, internalPort, desc, + * remoteHost) + * protocol is 'UDP' or 'TCP' */ +static PyObject * +UPnP_addportmapping(UPnPObject *self, PyObject *args) +{ + char extPort[6]; + unsigned short ePort; + char inPort[6]; + unsigned short iPort; + const char * proto; + const char * host; + const char * desc; + const char * remoteHost; + const char * leaseDuration = "0"; + int r; + if (!PyArg_ParseTuple(args, "HssHss", &ePort, &proto, + &host, &iPort, &desc, &remoteHost)) + return NULL; +Py_BEGIN_ALLOW_THREADS + sprintf(extPort, "%hu", ePort); + sprintf(inPort, "%hu", iPort); + r = UPNP_AddPortMapping(self->urls.controlURL, self->data.first.servicetype, + extPort, inPort, host, desc, proto, + remoteHost, leaseDuration); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) + { + Py_RETURN_TRUE; + } + else + { + // TODO: RAISE an Exception. See upnpcommands.h for errors codes. + // upnperrors.c + //Py_RETURN_FALSE; + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +/* AddAnyPortMapping(externalPort, protocol, internalHost, internalPort, desc, + * remoteHost) + * protocol is 'UDP' or 'TCP' */ +static PyObject * +UPnP_addanyportmapping(UPnPObject *self, PyObject *args) +{ + char extPort[6]; + unsigned short ePort; + char inPort[6]; + unsigned short iPort; + char reservedPort[6]; + const char * proto; + const char * host; + const char * desc; + const char * remoteHost; + const char * leaseDuration = "0"; + int r; + if (!PyArg_ParseTuple(args, "HssHss", &ePort, &proto, &host, &iPort, &desc, &remoteHost)) + return NULL; +Py_BEGIN_ALLOW_THREADS + sprintf(extPort, "%hu", ePort); + sprintf(inPort, "%hu", iPort); + r = UPNP_AddAnyPortMapping(self->urls.controlURL, self->data.first.servicetype, + extPort, inPort, host, desc, proto, + remoteHost, leaseDuration, reservedPort); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { + return Py_BuildValue("i", atoi(reservedPort)); + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + + +/* DeletePortMapping(extPort, proto, removeHost='') + * proto = 'UDP', 'TCP' */ +static PyObject * +UPnP_deleteportmapping(UPnPObject *self, PyObject *args) +{ + char extPort[6]; + unsigned short ePort; + const char * proto; + const char * remoteHost = ""; + int r; + if(!PyArg_ParseTuple(args, "Hs|z", &ePort, &proto, &remoteHost)) + return NULL; +Py_BEGIN_ALLOW_THREADS + sprintf(extPort, "%hu", ePort); + r = UPNP_DeletePortMapping(self->urls.controlURL, self->data.first.servicetype, + extPort, proto, remoteHost); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { + Py_RETURN_TRUE; + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +/* DeletePortMappingRange(extPort, proto, removeHost='') + * proto = 'UDP', 'TCP' */ +static PyObject * +UPnP_deleteportmappingrange(UPnPObject *self, PyObject *args) +{ + char extPortStart[6]; + unsigned short ePortStart; + char extPortEnd[6]; + unsigned short ePortEnd; + const char * proto; + unsigned char manage; + char manageStr[1]; + int r; + if(!PyArg_ParseTuple(args, "HHsb", &ePortStart, &ePortEnd, &proto, &manage)) + return NULL; +Py_BEGIN_ALLOW_THREADS + sprintf(extPortStart, "%hu", ePortStart); + sprintf(extPortEnd, "%hu", ePortEnd); + sprintf(manageStr, "%hhu", manage); + r = UPNP_DeletePortMappingRange(self->urls.controlURL, self->data.first.servicetype, + extPortStart, extPortEnd, proto, manageStr); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { + Py_RETURN_TRUE; + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +static PyObject * +UPnP_getportmappingnumberofentries(UPnPObject *self) +{ + unsigned int n = 0; + int r; +Py_BEGIN_ALLOW_THREADS + r = UPNP_GetPortMappingNumberOfEntries(self->urls.controlURL, + self->data.first.servicetype, + &n); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) { +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("I", n); +#else + return Py_BuildValue("i", (int)n); +#endif + } else { + /* TODO: have our own exception type ! */ + PyErr_SetString(PyExc_Exception, strupnperror(r)); + return NULL; + } +} + +/* GetSpecificPortMapping(ePort, proto, remoteHost='') + * proto = 'UDP' or 'TCP' */ +static PyObject * +UPnP_getspecificportmapping(UPnPObject *self, PyObject *args) +{ + char extPort[6]; + unsigned short ePort; + const char * proto; + const char * remoteHost = ""; + char intClient[40]; + char intPort[6]; + unsigned short iPort; + char desc[80]; + char enabled[4]; + char leaseDuration[16]; + if(!PyArg_ParseTuple(args, "Hs|z", &ePort, &proto, &remoteHost)) + return NULL; + extPort[0] = '\0'; intClient[0] = '\0'; intPort[0] = '\0'; + desc[0] = '\0'; enabled[0] = '\0'; leaseDuration[0] = '\0'; +Py_BEGIN_ALLOW_THREADS + sprintf(extPort, "%hu", ePort); + UPNP_GetSpecificPortMappingEntry(self->urls.controlURL, + self->data.first.servicetype, + extPort, proto, remoteHost, + intClient, intPort, + desc, enabled, leaseDuration); +Py_END_ALLOW_THREADS + if(intClient[0]) + { + iPort = (unsigned short)atoi(intPort); + return Py_BuildValue("(s,H,s,O,i)", + intClient, iPort, desc, + PyBool_FromLong(atoi(enabled)), + atoi(leaseDuration)); + } + else + { + Py_RETURN_NONE; + } +} + +/* GetGenericPortMapping(index) */ +static PyObject * +UPnP_getgenericportmapping(UPnPObject *self, PyObject *args) +{ + int i, r; + char index[8]; + char intClient[40]; + char intPort[6]; + unsigned short iPort; + char extPort[6]; + unsigned short ePort; + char protocol[4]; + char desc[80]; + char enabled[6]; + char rHost[64]; + char duration[16]; /* lease duration */ + unsigned int dur; + if(!PyArg_ParseTuple(args, "i", &i)) + return NULL; +Py_BEGIN_ALLOW_THREADS + snprintf(index, sizeof(index), "%d", i); + rHost[0] = '\0'; enabled[0] = '\0'; + duration[0] = '\0'; desc[0] = '\0'; + extPort[0] = '\0'; intPort[0] = '\0'; intClient[0] = '\0'; + r = UPNP_GetGenericPortMappingEntry(self->urls.controlURL, + self->data.first.servicetype, + index, + extPort, intClient, intPort, + protocol, desc, enabled, rHost, + duration); +Py_END_ALLOW_THREADS + if(r==UPNPCOMMAND_SUCCESS) + { + ePort = (unsigned short)atoi(extPort); + iPort = (unsigned short)atoi(intPort); + dur = (unsigned int)strtoul(duration, 0, 0); +#if (PY_MAJOR_VERSION >= 3) || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION > 3) + return Py_BuildValue("(H,s,(s,H),s,s,s,I)", + ePort, protocol, intClient, iPort, + desc, enabled, rHost, dur); +#else + return Py_BuildValue("(i,s,(s,i),s,s,s,i)", + (int)ePort, protocol, intClient, (int)iPort, + desc, enabled, rHost, (int)dur); +#endif + } + else + { + Py_RETURN_NONE; + } +} + +/* miniupnpc.UPnP object Method Table */ +static PyMethodDef UPnP_methods[] = { + {"discover", (PyCFunction)UPnP_discover, METH_NOARGS, + "discover UPnP IGD devices on the network" + }, + {"selectigd", (PyCFunction)UPnP_selectigd, METH_NOARGS, + "select a valid UPnP IGD among discovered devices" + }, + {"totalbytesent", (PyCFunction)UPnP_totalbytesent, METH_NOARGS, + "return the total number of bytes sent by UPnP IGD" + }, + {"totalbytereceived", (PyCFunction)UPnP_totalbytereceived, METH_NOARGS, + "return the total number of bytes received by UPnP IGD" + }, + {"totalpacketsent", (PyCFunction)UPnP_totalpacketsent, METH_NOARGS, + "return the total number of packets sent by UPnP IGD" + }, + {"totalpacketreceived", (PyCFunction)UPnP_totalpacketreceived, METH_NOARGS, + "return the total number of packets received by UPnP IGD" + }, + {"statusinfo", (PyCFunction)UPnP_statusinfo, METH_NOARGS, + "return status and uptime" + }, + {"connectiontype", (PyCFunction)UPnP_connectiontype, METH_NOARGS, + "return IGD WAN connection type" + }, + {"externalipaddress", (PyCFunction)UPnP_externalipaddress, METH_NOARGS, + "return external IP address" + }, + {"addportmapping", (PyCFunction)UPnP_addportmapping, METH_VARARGS, + "add a port mapping" + }, + {"addanyportmapping", (PyCFunction)UPnP_addanyportmapping, METH_VARARGS, + "add a port mapping, IGD to select alternative if necessary" + }, + {"deleteportmapping", (PyCFunction)UPnP_deleteportmapping, METH_VARARGS, + "delete a port mapping" + }, + {"deleteportmappingrange", (PyCFunction)UPnP_deleteportmappingrange, METH_VARARGS, + "delete a range of port mappings" + }, + {"getportmappingnumberofentries", (PyCFunction)UPnP_getportmappingnumberofentries, METH_NOARGS, + "-- non standard --" + }, + {"getspecificportmapping", (PyCFunction)UPnP_getspecificportmapping, METH_VARARGS, + "get details about a specific port mapping entry" + }, + {"getgenericportmapping", (PyCFunction)UPnP_getgenericportmapping, METH_VARARGS, + "get all details about the port mapping at index" + }, + {NULL} /* Sentinel */ +}; + +static PyTypeObject UPnPType = { + PyVarObject_HEAD_INIT(NULL, + 0) /*ob_size*/ + "miniupnpc.UPnP", /*tp_name*/ + sizeof(UPnPObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)UPnPObject_dealloc,/*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "UPnP objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + UPnP_methods, /* tp_methods */ + UPnP_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)UPnP_init, /* tp_init */ + 0, /* tp_alloc */ +#ifndef _WIN32 + PyType_GenericNew,/*UPnP_new,*/ /* tp_new */ +#else + 0, +#endif +}; + +/* module methods */ +static PyMethodDef miniupnpc_methods[] = { + {NULL} /* Sentinel */ +}; + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "miniupnpc", /* m_name */ + "miniupnpc module.", /* m_doc */ + -1, /* m_size */ + miniupnpc_methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ +}; +#endif + +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#define PyMODINIT_FUNC void +#endif + +PyMODINIT_FUNC +#if PY_MAJOR_VERSION >= 3 +PyInit_miniupnpc(void) +#else +initminiupnpc(void) +#endif +{ + PyObject* m; + +#ifdef _WIN32 + UPnPType.tp_new = PyType_GenericNew; +#endif + if (PyType_Ready(&UPnPType) < 0) +#if PY_MAJOR_VERSION >= 3 + return 0; +#else + return; +#endif + +#if PY_MAJOR_VERSION >= 3 + m = PyModule_Create(&moduledef); +#else + m = Py_InitModule3("miniupnpc", miniupnpc_methods, + "miniupnpc module."); +#endif + + Py_INCREF(&UPnPType); + PyModule_AddObject(m, "UPnP", (PyObject *)&UPnPType); + +#if PY_MAJOR_VERSION >= 3 + return m; +#endif +} + diff --git a/ext/miniupnpc/miniupnpcstrings.h.in b/ext/miniupnpc/miniupnpcstrings.h.in new file mode 100644 index 0000000..68bf429 --- /dev/null +++ b/ext/miniupnpc/miniupnpcstrings.h.in @@ -0,0 +1,23 @@ +/* $Id: miniupnpcstrings.h.in,v 1.6 2014/11/04 22:31:55 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author: Thomas Bernard + * Copyright (c) 2005-2014 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef MINIUPNPCSTRINGS_H_INCLUDED +#define MINIUPNPCSTRINGS_H_INCLUDED + +#define OS_STRING "OS/version" +#define MINIUPNPC_VERSION_STRING "version" + +#if 0 +/* according to "UPnP Device Architecture 1.0" */ +#define UPNP_VERSION_STRING "UPnP/1.0" +#else +/* according to "UPnP Device Architecture 1.1" */ +#define UPNP_VERSION_STRING "UPnP/1.1" +#endif + +#endif + diff --git a/ext/miniupnpc/miniupnpctypes.h b/ext/miniupnpc/miniupnpctypes.h new file mode 100644 index 0000000..591c32f --- /dev/null +++ b/ext/miniupnpc/miniupnpctypes.h @@ -0,0 +1,19 @@ +/* $Id: miniupnpctypes.h,v 1.2 2012/09/27 15:42:10 nanard Exp $ */ +/* Miniupnp project : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org + * Author : Thomas Bernard + * Copyright (c) 2011 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided within this distribution */ +#ifndef MINIUPNPCTYPES_H_INCLUDED +#define MINIUPNPCTYPES_H_INCLUDED + +#if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) +#define UNSIGNED_INTEGER unsigned long long +#define STRTOUI strtoull +#else +#define UNSIGNED_INTEGER unsigned int +#define STRTOUI strtoul +#endif + +#endif + diff --git a/ext/miniupnpc/miniwget.c b/ext/miniupnpc/miniwget.c new file mode 100644 index 0000000..93c8aa6 --- /dev/null +++ b/ext/miniupnpc/miniwget.c @@ -0,0 +1,666 @@ +/* $Id: miniwget.c,v 1.76 2016/12/16 08:54:04 nanard Exp $ */ +/* Project : miniupnp + * Website : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2016 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#include +#define MAXHOSTNAMELEN 64 +#define snprintf _snprintf +#define socklen_t int +#ifndef strncasecmp +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#define strncasecmp _memicmp +#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#define strncasecmp memicmp +#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#endif /* #ifndef strncasecmp */ +#else /* #ifdef _WIN32 */ +#include +#include +#if defined(__amigaos__) && !defined(__amigaos4__) +#define socklen_t int +#else /* #if defined(__amigaos__) && !defined(__amigaos4__) */ +#include +#endif /* #else defined(__amigaos__) && !defined(__amigaos4__) */ +#include +#include +#include +#include +#include +#define closesocket close +#include +#endif /* #else _WIN32 */ +#ifdef __GNU__ +#define MAXHOSTNAMELEN 64 +#endif /* __GNU__ */ + +#ifndef MIN +#define MIN(x,y) (((x)<(y))?(x):(y)) +#endif /* MIN */ + + +#ifdef _WIN32 +#define OS_STRING "Win32" +#define MINIUPNPC_VERSION_STRING "2.0" +#define UPNP_VERSION_STRING "UPnP/1.1" +#endif + +#include "miniwget.h" +#include "connecthostport.h" +#include "receivedata.h" + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 64 +#endif + +/* + * Read a HTTP response from a socket. + * Process Content-Length and Transfer-encoding headers. + * return a pointer to the content buffer, which length is saved + * to the length parameter. + */ +void * +getHTTPResponse(int s, int * size, int * status_code) +{ + char buf[2048]; + int n; + int endofheaders = 0; + int chunked = 0; + int content_length = -1; + unsigned int chunksize = 0; + unsigned int bytestocopy = 0; + /* buffers : */ + char * header_buf; + unsigned int header_buf_len = 2048; + unsigned int header_buf_used = 0; + char * content_buf; + unsigned int content_buf_len = 2048; + unsigned int content_buf_used = 0; + char chunksize_buf[32]; + unsigned int chunksize_buf_index; +#ifdef DEBUG + char * reason_phrase = NULL; + int reason_phrase_len = 0; +#endif + + if(status_code) *status_code = -1; + header_buf = malloc(header_buf_len); + if(header_buf == NULL) + { +#ifdef DEBUG + fprintf(stderr, "%s: Memory allocation error\n", "getHTTPResponse"); +#endif /* DEBUG */ + *size = -1; + return NULL; + } + content_buf = malloc(content_buf_len); + if(content_buf == NULL) + { + free(header_buf); +#ifdef DEBUG + fprintf(stderr, "%s: Memory allocation error\n", "getHTTPResponse"); +#endif /* DEBUG */ + *size = -1; + return NULL; + } + chunksize_buf[0] = '\0'; + chunksize_buf_index = 0; + + while((n = receivedata(s, buf, 2048, 5000, NULL)) > 0) + { + if(endofheaders == 0) + { + int i; + int linestart=0; + int colon=0; + int valuestart=0; + if(header_buf_used + n > header_buf_len) { + char * tmp = realloc(header_buf, header_buf_used + n); + if(tmp == NULL) { + /* memory allocation error */ + free(header_buf); + free(content_buf); + *size = -1; + return NULL; + } + header_buf = tmp; + header_buf_len = header_buf_used + n; + } + memcpy(header_buf + header_buf_used, buf, n); + header_buf_used += n; + /* search for CR LF CR LF (end of headers) + * recognize also LF LF */ + i = 0; + while(i < ((int)header_buf_used-1) && (endofheaders == 0)) { + if(header_buf[i] == '\r') { + i++; + if(header_buf[i] == '\n') { + i++; + if(i < (int)header_buf_used && header_buf[i] == '\r') { + i++; + if(i < (int)header_buf_used && header_buf[i] == '\n') { + endofheaders = i+1; + } + } + } + } else if(header_buf[i] == '\n') { + i++; + if(header_buf[i] == '\n') { + endofheaders = i+1; + } + } + i++; + } + if(endofheaders == 0) + continue; + /* parse header lines */ + for(i = 0; i < endofheaders - 1; i++) { + if(linestart > 0 && colon <= linestart && header_buf[i]==':') + { + colon = i; + while(i < (endofheaders-1) + && (header_buf[i+1] == ' ' || header_buf[i+1] == '\t')) + i++; + valuestart = i + 1; + } + /* detecting end of line */ + else if(header_buf[i]=='\r' || header_buf[i]=='\n') + { + if(linestart == 0 && status_code) + { + /* Status line + * HTTP-Version SP Status-Code SP Reason-Phrase CRLF */ + int sp; + for(sp = 0; sp < i; sp++) + if(header_buf[sp] == ' ') + { + if(*status_code < 0) + *status_code = atoi(header_buf + sp + 1); + else + { +#ifdef DEBUG + reason_phrase = header_buf + sp + 1; + reason_phrase_len = i - sp - 1; +#endif + break; + } + } +#ifdef DEBUG + printf("HTTP status code = %d, Reason phrase = %.*s\n", + *status_code, reason_phrase_len, reason_phrase); +#endif + } + else if(colon > linestart && valuestart > colon) + { +#ifdef DEBUG + printf("header='%.*s', value='%.*s'\n", + colon-linestart, header_buf+linestart, + i-valuestart, header_buf+valuestart); +#endif + if(0==strncasecmp(header_buf+linestart, "content-length", colon-linestart)) + { + content_length = atoi(header_buf+valuestart); +#ifdef DEBUG + printf("Content-Length: %d\n", content_length); +#endif + } + else if(0==strncasecmp(header_buf+linestart, "transfer-encoding", colon-linestart) + && 0==strncasecmp(header_buf+valuestart, "chunked", 7)) + { +#ifdef DEBUG + printf("chunked transfer-encoding!\n"); +#endif + chunked = 1; + } + } + while((i < (int)header_buf_used) && (header_buf[i]=='\r' || header_buf[i] == '\n')) + i++; + linestart = i; + colon = linestart; + valuestart = 0; + } + } + /* copy the remaining of the received data back to buf */ + n = header_buf_used - endofheaders; + memcpy(buf, header_buf + endofheaders, n); + /* if(headers) */ + } + if(endofheaders) + { + /* content */ + if(chunked) + { + int i = 0; + while(i < n) + { + if(chunksize == 0) + { + /* reading chunk size */ + if(chunksize_buf_index == 0) { + /* skipping any leading CR LF */ + if(i= '0' + && chunksize_buf[j] <= '9') + chunksize = (chunksize << 4) + (chunksize_buf[j] - '0'); + else + chunksize = (chunksize << 4) + ((chunksize_buf[j] | 32) - 'a' + 10); + } + chunksize_buf[0] = '\0'; + chunksize_buf_index = 0; + i++; + } else { + /* not finished to get chunksize */ + continue; + } +#ifdef DEBUG + printf("chunksize = %u (%x)\n", chunksize, chunksize); +#endif + if(chunksize == 0) + { +#ifdef DEBUG + printf("end of HTTP content - %d %d\n", i, n); + /*printf("'%.*s'\n", n-i, buf+i);*/ +#endif + goto end_of_stream; + } + } + bytestocopy = ((int)chunksize < (n - i))?chunksize:(unsigned int)(n - i); + if((content_buf_used + bytestocopy) > content_buf_len) + { + char * tmp; + if(content_length >= (int)(content_buf_used + bytestocopy)) { + content_buf_len = content_length; + } else { + content_buf_len = content_buf_used + bytestocopy; + } + tmp = realloc(content_buf, content_buf_len); + if(tmp == NULL) { + /* memory allocation error */ + free(content_buf); + free(header_buf); + *size = -1; + return NULL; + } + content_buf = tmp; + } + memcpy(content_buf + content_buf_used, buf + i, bytestocopy); + content_buf_used += bytestocopy; + i += bytestocopy; + chunksize -= bytestocopy; + } + } + else + { + /* not chunked */ + if(content_length > 0 + && (int)(content_buf_used + n) > content_length) { + /* skipping additional bytes */ + n = content_length - content_buf_used; + } + if(content_buf_used + n > content_buf_len) + { + char * tmp; + if(content_length >= (int)(content_buf_used + n)) { + content_buf_len = content_length; + } else { + content_buf_len = content_buf_used + n; + } + tmp = realloc(content_buf, content_buf_len); + if(tmp == NULL) { + /* memory allocation error */ + free(content_buf); + free(header_buf); + *size = -1; + return NULL; + } + content_buf = tmp; + } + memcpy(content_buf + content_buf_used, buf, n); + content_buf_used += n; + } + } + /* use the Content-Length header value if available */ + if(content_length > 0 && (int)content_buf_used >= content_length) + { +#ifdef DEBUG + printf("End of HTTP content\n"); +#endif + break; + } + } +end_of_stream: + free(header_buf); header_buf = NULL; + *size = content_buf_used; + if(content_buf_used == 0) + { + free(content_buf); + content_buf = NULL; + } + return content_buf; +} + +/* miniwget3() : + * do all the work. + * Return NULL if something failed. */ +static void * +miniwget3(const char * host, + unsigned short port, const char * path, + int * size, char * addr_str, int addr_str_len, + const char * httpversion, unsigned int scope_id, + int * status_code) +{ + char buf[2048]; + int s; + int n; + int len; + int sent; + void * content; + + *size = 0; + s = connecthostport(host, port, scope_id); + if(s < 0) + return NULL; + + /* get address for caller ! */ + if(addr_str) + { + struct sockaddr_storage saddr; + socklen_t saddrlen; + + saddrlen = sizeof(saddr); + if(getsockname(s, (struct sockaddr *)&saddr, &saddrlen) < 0) + { + perror("getsockname"); + } + else + { +#if defined(__amigaos__) && !defined(__amigaos4__) + /* using INT WINAPI WSAAddressToStringA(LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFOA, LPSTR, LPDWORD); + * But his function make a string with the port : nn.nn.nn.nn:port */ +/* if(WSAAddressToStringA((SOCKADDR *)&saddr, sizeof(saddr), + NULL, addr_str, (DWORD *)&addr_str_len)) + { + printf("WSAAddressToStringA() failed : %d\n", WSAGetLastError()); + }*/ + /* the following code is only compatible with ip v4 addresses */ + strncpy(addr_str, inet_ntoa(((struct sockaddr_in *)&saddr)->sin_addr), addr_str_len); +#else +#if 0 + if(saddr.sa_family == AF_INET6) { + inet_ntop(AF_INET6, + &(((struct sockaddr_in6 *)&saddr)->sin6_addr), + addr_str, addr_str_len); + } else { + inet_ntop(AF_INET, + &(((struct sockaddr_in *)&saddr)->sin_addr), + addr_str, addr_str_len); + } +#endif + /* getnameinfo return ip v6 address with the scope identifier + * such as : 2a01:e35:8b2b:7330::%4281128194 */ + n = getnameinfo((const struct sockaddr *)&saddr, saddrlen, + addr_str, addr_str_len, + NULL, 0, + NI_NUMERICHOST | NI_NUMERICSERV); + if(n != 0) { +#ifdef _WIN32 + fprintf(stderr, "getnameinfo() failed : %d\n", n); +#else + fprintf(stderr, "getnameinfo() failed : %s\n", gai_strerror(n)); +#endif + } +#endif + } +#ifdef DEBUG + printf("address miniwget : %s\n", addr_str); +#endif + } + + len = snprintf(buf, sizeof(buf), + "GET %s HTTP/%s\r\n" + "Host: %s:%d\r\n" + "Connection: Close\r\n" + "User-Agent: " OS_STRING ", " UPNP_VERSION_STRING ", MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" + + "\r\n", + path, httpversion, host, port); + if ((unsigned int)len >= sizeof(buf)) + { + closesocket(s); + return NULL; + } + sent = 0; + /* sending the HTTP request */ + while(sent < len) + { + n = send(s, buf+sent, len-sent, 0); + if(n < 0) + { + perror("send"); + closesocket(s); + return NULL; + } + else + { + sent += n; + } + } + content = getHTTPResponse(s, size, status_code); + closesocket(s); + return content; +} + +/* miniwget2() : + * Call miniwget3(); retry with HTTP/1.1 if 1.0 fails. */ +static void * +miniwget2(const char * host, + unsigned short port, const char * path, + int * size, char * addr_str, int addr_str_len, + unsigned int scope_id, int * status_code) +{ + char * respbuffer; + +#if 1 + respbuffer = miniwget3(host, port, path, size, + addr_str, addr_str_len, "1.1", + scope_id, status_code); +#else + respbuffer = miniwget3(host, port, path, size, + addr_str, addr_str_len, "1.0", + scope_id, status_code); + if (*size == 0) + { +#ifdef DEBUG + printf("Retrying with HTTP/1.1\n"); +#endif + free(respbuffer); + respbuffer = miniwget3(host, port, path, size, + addr_str, addr_str_len, "1.1", + scope_id, status_code); + } +#endif + return respbuffer; +} + + + + +/* parseURL() + * arguments : + * url : source string not modified + * hostname : hostname destination string (size of MAXHOSTNAMELEN+1) + * port : port (destination) + * path : pointer to the path part of the URL + * + * Return values : + * 0 - Failure + * 1 - Success */ +int +parseURL(const char * url, + char * hostname, unsigned short * port, + char * * path, unsigned int * scope_id) +{ + char * p1, *p2, *p3; + if(!url) + return 0; + p1 = strstr(url, "://"); + if(!p1) + return 0; + p1 += 3; + if( (url[0]!='h') || (url[1]!='t') + ||(url[2]!='t') || (url[3]!='p')) + return 0; + memset(hostname, 0, MAXHOSTNAMELEN + 1); + if(*p1 == '[') + { + /* IP v6 : http://[2a00:1450:8002::6a]/path/abc */ + char * scope; + scope = strchr(p1, '%'); + p2 = strchr(p1, ']'); + if(p2 && scope && scope < p2 && scope_id) { + /* parse scope */ +#ifdef IF_NAMESIZE + char tmp[IF_NAMESIZE]; + int l; + scope++; + /* "%25" is just '%' in URL encoding */ + if(scope[0] == '2' && scope[1] == '5') + scope += 2; /* skip "25" */ + l = p2 - scope; + if(l >= IF_NAMESIZE) + l = IF_NAMESIZE - 1; + memcpy(tmp, scope, l); + tmp[l] = '\0'; + *scope_id = if_nametoindex(tmp); + if(*scope_id == 0) { + *scope_id = (unsigned int)strtoul(tmp, NULL, 10); + } +#else + /* under windows, scope is numerical */ + char tmp[8]; + int l; + scope++; + /* "%25" is just '%' in URL encoding */ + if(scope[0] == '2' && scope[1] == '5') + scope += 2; /* skip "25" */ + l = p2 - scope; + if(l >= sizeof(tmp)) + l = sizeof(tmp) - 1; + memcpy(tmp, scope, l); + tmp[l] = '\0'; + *scope_id = (unsigned int)strtoul(tmp, NULL, 10); +#endif + } + p3 = strchr(p1, '/'); + if(p2 && p3) + { + p2++; + strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1))); + if(*p2 == ':') + { + *port = 0; + p2++; + while( (*p2 >= '0') && (*p2 <= '9')) + { + *port *= 10; + *port += (unsigned short)(*p2 - '0'); + p2++; + } + } + else + { + *port = 80; + } + *path = p3; + return 1; + } + } + p2 = strchr(p1, ':'); + p3 = strchr(p1, '/'); + if(!p3) + return 0; + if(!p2 || (p2>p3)) + { + strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p3-p1))); + *port = 80; + } + else + { + strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1))); + *port = 0; + p2++; + while( (*p2 >= '0') && (*p2 <= '9')) + { + *port *= 10; + *port += (unsigned short)(*p2 - '0'); + p2++; + } + } + *path = p3; + return 1; +} + +void * +miniwget(const char * url, int * size, + unsigned int scope_id, int * status_code) +{ + unsigned short port; + char * path; + /* protocol://host:port/chemin */ + char hostname[MAXHOSTNAMELEN+1]; + *size = 0; + if(!parseURL(url, hostname, &port, &path, &scope_id)) + return NULL; +#ifdef DEBUG + printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n", + hostname, port, path, scope_id); +#endif + return miniwget2(hostname, port, path, size, 0, 0, scope_id, status_code); +} + +void * +miniwget_getaddr(const char * url, int * size, + char * addr, int addrlen, unsigned int scope_id, + int * status_code) +{ + unsigned short port; + char * path; + /* protocol://host:port/path */ + char hostname[MAXHOSTNAMELEN+1]; + *size = 0; + if(addr) + addr[0] = '\0'; + if(!parseURL(url, hostname, &port, &path, &scope_id)) + return NULL; +#ifdef DEBUG + printf("parsed url : hostname='%s' port=%hu path='%s' scope_id=%u\n", + hostname, port, path, scope_id); +#endif + return miniwget2(hostname, port, path, size, addr, addrlen, scope_id, status_code); +} + diff --git a/ext/miniupnpc/miniwget.h b/ext/miniupnpc/miniwget.h new file mode 100644 index 0000000..0701494 --- /dev/null +++ b/ext/miniupnpc/miniwget.h @@ -0,0 +1,30 @@ +/* $Id: miniwget.h,v 1.12 2016/01/24 17:24:36 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2016 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef MINIWGET_H_INCLUDED +#define MINIWGET_H_INCLUDED + +#include "miniupnpc_declspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +MINIUPNP_LIBSPEC void * getHTTPResponse(int s, int * size, int * status_code); + +MINIUPNP_LIBSPEC void * miniwget(const char *, int *, unsigned int, int *); + +MINIUPNP_LIBSPEC void * miniwget_getaddr(const char *, int *, char *, int, unsigned int, int *); + +int parseURL(const char *, char *, unsigned short *, char * *, unsigned int *); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/ext/miniupnpc/minixml.c b/ext/miniupnpc/minixml.c new file mode 100644 index 0000000..3e201ec --- /dev/null +++ b/ext/miniupnpc/minixml.c @@ -0,0 +1,229 @@ +/* $Id: minixml.c,v 1.11 2014/02/03 15:54:12 nanard Exp $ */ +/* minixml.c : the minimum size a xml parser can be ! */ +/* Project : miniupnp + * webpage: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author : Thomas Bernard + +Copyright (c) 2005-2014, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include "minixml.h" + +/* parseatt : used to parse the argument list + * return 0 (false) in case of success and -1 (true) if the end + * of the xmlbuffer is reached. */ +static int parseatt(struct xmlparser * p) +{ + const char * attname; + int attnamelen; + const char * attvalue; + int attvaluelen; + while(p->xml < p->xmlend) + { + if(*p->xml=='/' || *p->xml=='>') + return 0; + if( !IS_WHITE_SPACE(*p->xml) ) + { + char sep; + attname = p->xml; + attnamelen = 0; + while(*p->xml!='=' && !IS_WHITE_SPACE(*p->xml) ) + { + attnamelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + while(*(p->xml++) != '=') + { + if(p->xml >= p->xmlend) + return -1; + } + while(IS_WHITE_SPACE(*p->xml)) + { + p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + sep = *p->xml; + if(sep=='\'' || sep=='\"') + { + p->xml++; + if(p->xml >= p->xmlend) + return -1; + attvalue = p->xml; + attvaluelen = 0; + while(*p->xml != sep) + { + attvaluelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + } + else + { + attvalue = p->xml; + attvaluelen = 0; + while( !IS_WHITE_SPACE(*p->xml) + && *p->xml != '>' && *p->xml != '/') + { + attvaluelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + } + /*printf("%.*s='%.*s'\n", + attnamelen, attname, attvaluelen, attvalue);*/ + if(p->attfunc) + p->attfunc(p->data, attname, attnamelen, attvalue, attvaluelen); + } + p->xml++; + } + return -1; +} + +/* parseelt parse the xml stream and + * call the callback functions when needed... */ +static void parseelt(struct xmlparser * p) +{ + int i; + const char * elementname; + while(p->xml < (p->xmlend - 1)) + { + if((p->xml + 4) <= p->xmlend && (0 == memcmp(p->xml, "", 3) != 0); + p->xml += 3; + } + else if((p->xml)[0]=='<' && (p->xml)[1]!='?') + { + i = 0; elementname = ++p->xml; + while( !IS_WHITE_SPACE(*p->xml) + && (*p->xml!='>') && (*p->xml!='/') + ) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + /* to ignore namespace : */ + if(*p->xml==':') + { + i = 0; + elementname = ++p->xml; + } + } + if(i>0) + { + if(p->starteltfunc) + p->starteltfunc(p->data, elementname, i); + if(parseatt(p)) + return; + if(*p->xml!='/') + { + const char * data; + i = 0; data = ++p->xml; + if (p->xml >= p->xmlend) + return; + while( IS_WHITE_SPACE(*p->xml) ) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + } + if(memcmp(p->xml, "xml += 9; + data = p->xml; + i = 0; + while(memcmp(p->xml, "]]>", 3) != 0) + { + i++; p->xml++; + if ((p->xml + 3) >= p->xmlend) + return; + } + if(i>0 && p->datafunc) + p->datafunc(p->data, data, i); + while(*p->xml!='<') + { + p->xml++; + if (p->xml >= p->xmlend) + return; + } + } + else + { + while(*p->xml!='<') + { + i++; p->xml++; + if ((p->xml + 1) >= p->xmlend) + return; + } + if(i>0 && p->datafunc && *(p->xml + 1) == '/') + p->datafunc(p->data, data, i); + } + } + } + else if(*p->xml == '/') + { + i = 0; elementname = ++p->xml; + if (p->xml >= p->xmlend) + return; + while((*p->xml != '>')) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + } + if(p->endeltfunc) + p->endeltfunc(p->data, elementname, i); + p->xml++; + } + } + else + { + p->xml++; + } + } +} + +/* the parser must be initialized before calling this function */ +void parsexml(struct xmlparser * parser) +{ + parser->xml = parser->xmlstart; + parser->xmlend = parser->xmlstart + parser->xmlsize; + parseelt(parser); +} + + diff --git a/ext/miniupnpc/minixml.h b/ext/miniupnpc/minixml.h new file mode 100644 index 0000000..9f43aa4 --- /dev/null +++ b/ext/miniupnpc/minixml.h @@ -0,0 +1,37 @@ +/* $Id: minixml.h,v 1.7 2012/09/27 15:42:10 nanard Exp $ */ +/* minimal xml parser + * + * Project : miniupnp + * Website : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef MINIXML_H_INCLUDED +#define MINIXML_H_INCLUDED +#define IS_WHITE_SPACE(c) ((c==' ') || (c=='\t') || (c=='\r') || (c=='\n')) + +/* if a callback function pointer is set to NULL, + * the function is not called */ +struct xmlparser { + const char *xmlstart; + const char *xmlend; + const char *xml; /* pointer to current character */ + int xmlsize; + void * data; + void (*starteltfunc) (void *, const char *, int); + void (*endeltfunc) (void *, const char *, int); + void (*datafunc) (void *, const char *, int); + void (*attfunc) (void *, const char *, int, const char *, int); +}; + +/* parsexml() + * the xmlparser structure must be initialized before the call + * the following structure members have to be initialized : + * xmlstart, xmlsize, data, *func + * xml is for internal usage, xmlend is computed automatically */ +void parsexml(struct xmlparser *); + +#endif + diff --git a/ext/miniupnpc/minixmlvalid.c b/ext/miniupnpc/minixmlvalid.c new file mode 100644 index 0000000..dad1488 --- /dev/null +++ b/ext/miniupnpc/minixmlvalid.c @@ -0,0 +1,163 @@ +/* $Id: minixmlvalid.c,v 1.7 2015/07/15 12:41:15 nanard Exp $ */ +/* MiniUPnP Project + * http://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/ + * minixmlvalid.c : + * validation program for the minixml parser + * + * (c) 2006-2011 Thomas Bernard */ + +#include +#include +#include +#include "minixml.h" + +/* xml event structure */ +struct event { + enum { ELTSTART, ELTEND, ATT, CHARDATA } type; + const char * data; + int len; +}; + +struct eventlist { + int n; + struct event * events; +}; + +/* compare 2 xml event lists + * return 0 if the two lists are equals */ +int evtlistcmp(struct eventlist * a, struct eventlist * b) +{ + int i; + struct event * ae, * be; + if(a->n != b->n) + { + printf("event number not matching : %d != %d\n", a->n, b->n); + /*return 1;*/ + } + for(i=0; in; i++) + { + ae = a->events + i; + be = b->events + i; + if( (ae->type != be->type) + ||(ae->len != be->len) + ||memcmp(ae->data, be->data, ae->len)) + { + printf("Found a difference : %d '%.*s' != %d '%.*s'\n", + ae->type, ae->len, ae->data, + be->type, be->len, be->data); + return 1; + } + } + return 0; +} + +/* Test data */ +static const char xmldata[] = +"\n" +" " +"character data" +" \n \t" +"" +"\nstuff !\n ]]> \n\n" +" \tchardata1 chardata2 " +""; + +static const struct event evtref[] = +{ + {ELTSTART, "xmlroot", 7}, + {ELTSTART, "elt1", 4}, + /* attributes */ + {CHARDATA, "character data", 14}, + {ELTEND, "elt1", 4}, + {ELTSTART, "elt1b", 5}, + {ELTSTART, "elt1", 4}, + {CHARDATA, " stuff !\n ", 16}, + {ELTEND, "elt1", 4}, + {ELTSTART, "elt2a", 5}, + {ELTSTART, "elt2b", 5}, + {CHARDATA, "chardata1", 9}, + {ELTEND, "elt2b", 5}, + {ELTSTART, "elt2b", 5}, + {CHARDATA, " chardata2 ", 11}, + {ELTEND, "elt2b", 5}, + {ELTEND, "elt2a", 5}, + {ELTEND, "xmlroot", 7} +}; + +void startelt(void * data, const char * p, int l) +{ + struct eventlist * evtlist = data; + struct event * evt; + evt = evtlist->events + evtlist->n; + /*printf("startelt : %.*s\n", l, p);*/ + evt->type = ELTSTART; + evt->data = p; + evt->len = l; + evtlist->n++; +} + +void endelt(void * data, const char * p, int l) +{ + struct eventlist * evtlist = data; + struct event * evt; + evt = evtlist->events + evtlist->n; + /*printf("endelt : %.*s\n", l, p);*/ + evt->type = ELTEND; + evt->data = p; + evt->len = l; + evtlist->n++; +} + +void chardata(void * data, const char * p, int l) +{ + struct eventlist * evtlist = data; + struct event * evt; + evt = evtlist->events + evtlist->n; + /*printf("chardata : '%.*s'\n", l, p);*/ + evt->type = CHARDATA; + evt->data = p; + evt->len = l; + evtlist->n++; +} + +int testxmlparser(const char * xml, int size) +{ + int r; + struct eventlist evtlist; + struct eventlist evtlistref; + struct xmlparser parser; + evtlist.n = 0; + evtlist.events = malloc(sizeof(struct event)*100); + if(evtlist.events == NULL) + { + fprintf(stderr, "Memory allocation error.\n"); + return -1; + } + memset(&parser, 0, sizeof(parser)); + parser.xmlstart = xml; + parser.xmlsize = size; + parser.data = &evtlist; + parser.starteltfunc = startelt; + parser.endeltfunc = endelt; + parser.datafunc = chardata; + parsexml(&parser); + printf("%d events\n", evtlist.n); + /* compare */ + evtlistref.n = sizeof(evtref)/sizeof(struct event); + evtlistref.events = (struct event *)evtref; + r = evtlistcmp(&evtlistref, &evtlist); + free(evtlist.events); + return r; +} + +int main(int argc, char * * argv) +{ + int r; + (void)argc; (void)argv; + + r = testxmlparser(xmldata, sizeof(xmldata)-1); + if(r) + printf("minixml validation test failed\n"); + return r; +} + diff --git a/ext/miniupnpc/portlistingparse.c b/ext/miniupnpc/portlistingparse.c new file mode 100644 index 0000000..d1954f5 --- /dev/null +++ b/ext/miniupnpc/portlistingparse.c @@ -0,0 +1,172 @@ +/* $Id: portlistingparse.c,v 1.10 2016/12/16 08:53:21 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2011-2016 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ +#include +#include +#ifdef DEBUG +#include +#endif /* DEBUG */ +#include "portlistingparse.h" +#include "minixml.h" + +/* list of the elements */ +static const struct { + const portMappingElt code; + const char * const str; +} elements[] = { + { PortMappingEntry, "PortMappingEntry"}, + { NewRemoteHost, "NewRemoteHost"}, + { NewExternalPort, "NewExternalPort"}, + { NewProtocol, "NewProtocol"}, + { NewInternalPort, "NewInternalPort"}, + { NewInternalClient, "NewInternalClient"}, + { NewEnabled, "NewEnabled"}, + { NewDescription, "NewDescription"}, + { NewLeaseTime, "NewLeaseTime"}, + { PortMappingEltNone, NULL} +}; + +/* Helper function */ +static UNSIGNED_INTEGER +atoui(const char * p, int l) +{ + UNSIGNED_INTEGER r = 0; + while(l > 0 && *p) + { + if(*p >= '0' && *p <= '9') + r = r*10 + (*p - '0'); + else + break; + p++; + l--; + } + return r; +} + +/* Start element handler */ +static void +startelt(void * d, const char * name, int l) +{ + int i; + struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; + pdata->curelt = PortMappingEltNone; + for(i = 0; elements[i].str; i++) + { + if(strlen(elements[i].str) == (size_t)l && memcmp(name, elements[i].str, l) == 0) + { + pdata->curelt = elements[i].code; + break; + } + } + if(pdata->curelt == PortMappingEntry) + { + struct PortMapping * pm; + pm = calloc(1, sizeof(struct PortMapping)); + if(pm == NULL) + { + /* malloc error */ +#ifdef DEBUG + fprintf(stderr, "%s: error allocating memory", + "startelt"); +#endif /* DEBUG */ + return; + } + pm->l_next = pdata->l_head; /* insert in list */ + pdata->l_head = pm; + } +} + +/* End element handler */ +static void +endelt(void * d, const char * name, int l) +{ + struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; + (void)name; + (void)l; + pdata->curelt = PortMappingEltNone; +} + +/* Data handler */ +static void +data(void * d, const char * data, int l) +{ + struct PortMapping * pm; + struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; + pm = pdata->l_head; + if(!pm) + return; + if(l > 63) + l = 63; + switch(pdata->curelt) + { + case NewRemoteHost: + memcpy(pm->remoteHost, data, l); + pm->remoteHost[l] = '\0'; + break; + case NewExternalPort: + pm->externalPort = (unsigned short)atoui(data, l); + break; + case NewProtocol: + if(l > 3) + l = 3; + memcpy(pm->protocol, data, l); + pm->protocol[l] = '\0'; + break; + case NewInternalPort: + pm->internalPort = (unsigned short)atoui(data, l); + break; + case NewInternalClient: + memcpy(pm->internalClient, data, l); + pm->internalClient[l] = '\0'; + break; + case NewEnabled: + pm->enabled = (unsigned char)atoui(data, l); + break; + case NewDescription: + memcpy(pm->description, data, l); + pm->description[l] = '\0'; + break; + case NewLeaseTime: + pm->leaseTime = atoui(data, l); + break; + default: + break; + } +} + + +/* Parse the PortMappingList XML document for IGD version 2 + */ +void +ParsePortListing(const char * buffer, int bufsize, + struct PortMappingParserData * pdata) +{ + struct xmlparser parser; + + memset(pdata, 0, sizeof(struct PortMappingParserData)); + /* init xmlparser */ + parser.xmlstart = buffer; + parser.xmlsize = bufsize; + parser.data = pdata; + parser.starteltfunc = startelt; + parser.endeltfunc = endelt; + parser.datafunc = data; + parser.attfunc = 0; + parsexml(&parser); +} + +void +FreePortListing(struct PortMappingParserData * pdata) +{ + struct PortMapping * pm; + while((pm = pdata->l_head) != NULL) + { + /* remove from list */ + pdata->l_head = pm->l_next; + free(pm); + } +} + diff --git a/ext/miniupnpc/portlistingparse.h b/ext/miniupnpc/portlistingparse.h new file mode 100644 index 0000000..661ad1f --- /dev/null +++ b/ext/miniupnpc/portlistingparse.h @@ -0,0 +1,65 @@ +/* $Id: portlistingparse.h,v 1.11 2015/07/21 13:16:55 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2011-2015 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ +#ifndef PORTLISTINGPARSE_H_INCLUDED +#define PORTLISTINGPARSE_H_INCLUDED + +#include "miniupnpc_declspec.h" +/* for the definition of UNSIGNED_INTEGER */ +#include "miniupnpctypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* sample of PortMappingEntry : + + 202.233.2.1 + 2345 + TCP + 2345 + 192.168.1.137 + 1 + dooom + 345 + + */ +typedef enum { PortMappingEltNone, + PortMappingEntry, NewRemoteHost, + NewExternalPort, NewProtocol, + NewInternalPort, NewInternalClient, + NewEnabled, NewDescription, + NewLeaseTime } portMappingElt; + +struct PortMapping { + struct PortMapping * l_next; /* list next element */ + UNSIGNED_INTEGER leaseTime; + unsigned short externalPort; + unsigned short internalPort; + char remoteHost[64]; + char internalClient[64]; + char description[64]; + char protocol[4]; + unsigned char enabled; +}; + +struct PortMappingParserData { + struct PortMapping * l_head; /* list head */ + portMappingElt curelt; +}; + +MINIUPNP_LIBSPEC void +ParsePortListing(const char * buffer, int bufsize, + struct PortMappingParserData * pdata); + +MINIUPNP_LIBSPEC void +FreePortListing(struct PortMappingParserData * pdata); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/miniupnpc/pymoduletest.py b/ext/miniupnpc/pymoduletest.py new file mode 100644 index 0000000..9fddd9c --- /dev/null +++ b/ext/miniupnpc/pymoduletest.py @@ -0,0 +1,88 @@ +#! /usr/bin/python +# vim: tabstop=2 shiftwidth=2 expandtab +# MiniUPnP project +# Author : Thomas Bernard +# This Sample code is public domain. +# website : http://miniupnp.tuxfamily.org/ + +# import the python miniupnpc module +import miniupnpc +import sys + +try: + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('-m', '--multicastif') + parser.add_argument('-p', '--minissdpdsocket') + parser.add_argument('-d', '--discoverdelay', type=int, default=200) + parser.add_argument('-z', '--localport', type=int, default=0) + # create the object + u = miniupnpc.UPnP(**vars(parser.parse_args())) +except: + print 'argparse not available' + i = 1 + multicastif = None + minissdpdsocket = None + discoverdelay = 200 + localport = 0 + while i < len(sys.argv): + print sys.argv[i] + if sys.argv[i] == '-m' or sys.argv[i] == '--multicastif': + multicastif = sys.argv[i+1] + elif sys.argv[i] == '-p' or sys.argv[i] == '--minissdpdsocket': + minissdpdsocket = sys.argv[i+1] + elif sys.argv[i] == '-d' or sys.argv[i] == '--discoverdelay': + discoverdelay = int(sys.argv[i+1]) + elif sys.argv[i] == '-z' or sys.argv[i] == '--localport': + localport = int(sys.argv[i+1]) + else: + raise Exception('invalid argument %s' % sys.argv[i]) + i += 2 + # create the object + u = miniupnpc.UPnP(multicastif, minissdpdsocket, discoverdelay, localport) + +print 'inital(default) values :' +print ' discoverdelay', u.discoverdelay +print ' lanaddr', u.lanaddr +print ' multicastif', u.multicastif +print ' minissdpdsocket', u.minissdpdsocket +#u.minissdpdsocket = '../minissdpd/minissdpd.sock' +# discovery process, it usualy takes several seconds (2 seconds or more) +print 'Discovering... delay=%ums' % u.discoverdelay +print u.discover(), 'device(s) detected' +# select an igd +try: + u.selectigd() +except Exception, e: + print 'Exception :', e + sys.exit(1) +# display information about the IGD and the internet connection +print 'local ip address :', u.lanaddr +print 'external ip address :', u.externalipaddress() +print u.statusinfo(), u.connectiontype() +print 'total bytes : sent', u.totalbytesent(), 'received', u.totalbytereceived() +print 'total packets : sent', u.totalpacketsent(), 'received', u.totalpacketreceived() + +#print u.addportmapping(64000, 'TCP', +# '192.168.1.166', 63000, 'port mapping test', '') +#print u.deleteportmapping(64000, 'TCP') + +port = 0 +proto = 'UDP' +# list the redirections : +i = 0 +while True: + p = u.getgenericportmapping(i) + if p==None: + break + print i, p + (port, proto, (ihost,iport), desc, c, d, e) = p + #print port, desc + i = i + 1 + +print u.getspecificportmapping(port, proto) +try: + print u.getportmappingnumberofentries() +except Exception, e: + print 'GetPortMappingNumberOfEntries() is not supported :', e + diff --git a/ext/miniupnpc/receivedata.c b/ext/miniupnpc/receivedata.c new file mode 100644 index 0000000..ef85a3d --- /dev/null +++ b/ext/miniupnpc/receivedata.c @@ -0,0 +1,105 @@ +/* $Id: receivedata.c,v 1.7 2015/11/09 21:51:41 nanard Exp $ */ +/* Project : miniupnp + * Website : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2011-2014 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include +#include +#ifdef _WIN32 +#include +#include +#else /* _WIN32 */ +#include +#if defined(__amigaos__) && !defined(__amigaos4__) +#define socklen_t int +#else /* #if defined(__amigaos__) && !defined(__amigaos4__) */ +#include +#endif /* #else defined(__amigaos__) && !defined(__amigaos4__) */ +#include +#include +#if !defined(__amigaos__) && !defined(__amigaos4__) +#include +#endif /* !defined(__amigaos__) && !defined(__amigaos4__) */ +#include +#define MINIUPNPC_IGNORE_EINTR +#endif /* _WIN32 */ + +#ifdef _WIN32 +#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); +#else +#define PRINT_SOCKET_ERROR(x) perror(x) +#endif + +#include "receivedata.h" + +int +receivedata(int socket, + char * data, int length, + int timeout, unsigned int * scope_id) +{ +#ifdef MINIUPNPC_GET_SRC_ADDR + struct sockaddr_storage src_addr; + socklen_t src_addr_len = sizeof(src_addr); +#endif /* MINIUPNPC_GET_SRC_ADDR */ + int n; +#if !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) + /* using poll */ + struct pollfd fds[1]; /* for the poll */ +#ifdef MINIUPNPC_IGNORE_EINTR + do { +#endif /* MINIUPNPC_IGNORE_EINTR */ + fds[0].fd = socket; + fds[0].events = POLLIN; + n = poll(fds, 1, timeout); +#ifdef MINIUPNPC_IGNORE_EINTR + } while(n < 0 && errno == EINTR); +#endif /* MINIUPNPC_IGNORE_EINTR */ + if(n < 0) { + PRINT_SOCKET_ERROR("poll"); + return -1; + } else if(n == 0) { + /* timeout */ + return 0; + } +#else /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ + /* using select under _WIN32 and amigaos */ + fd_set socketSet; + TIMEVAL timeval; + FD_ZERO(&socketSet); + FD_SET(socket, &socketSet); + timeval.tv_sec = timeout / 1000; + timeval.tv_usec = (timeout % 1000) * 1000; + n = select(FD_SETSIZE, &socketSet, NULL, NULL, &timeval); + if(n < 0) { + PRINT_SOCKET_ERROR("select"); + return -1; + } else if(n == 0) { + return 0; + } +#endif /* !defined(_WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ +#ifdef MINIUPNPC_GET_SRC_ADDR + memset(&src_addr, 0, sizeof(src_addr)); + n = recvfrom(socket, data, length, 0, + (struct sockaddr *)&src_addr, &src_addr_len); +#else /* MINIUPNPC_GET_SRC_ADDR */ + n = recv(socket, data, length, 0); +#endif /* MINIUPNPC_GET_SRC_ADDR */ + if(n<0) { + PRINT_SOCKET_ERROR("recv"); + } +#ifdef MINIUPNPC_GET_SRC_ADDR + if (src_addr.ss_family == AF_INET6) { + const struct sockaddr_in6 * src_addr6 = (struct sockaddr_in6 *)&src_addr; +#ifdef DEBUG + printf("scope_id=%u\n", src_addr6->sin6_scope_id); +#endif /* DEBUG */ + if(scope_id) + *scope_id = src_addr6->sin6_scope_id; + } +#endif /* MINIUPNPC_GET_SRC_ADDR */ + return n; +} + diff --git a/ext/miniupnpc/receivedata.h b/ext/miniupnpc/receivedata.h new file mode 100644 index 0000000..0520a11 --- /dev/null +++ b/ext/miniupnpc/receivedata.h @@ -0,0 +1,19 @@ +/* $Id: receivedata.h,v 1.4 2012/09/27 15:42:10 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author: Thomas Bernard + * Copyright (c) 2011-2012 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef RECEIVEDATA_H_INCLUDED +#define RECEIVEDATA_H_INCLUDED + +/* Reads data from the specified socket. + * Returns the number of bytes read if successful, zero if no bytes were + * read or if we timed out. Returns negative if there was an error. */ +int receivedata(int socket, + char * data, int length, + int timeout, unsigned int * scope_id); + +#endif + diff --git a/ext/miniupnpc/setup.py b/ext/miniupnpc/setup.py new file mode 100644 index 0000000..97e42bf --- /dev/null +++ b/ext/miniupnpc/setup.py @@ -0,0 +1,28 @@ +#! /usr/bin/python +# vim: tabstop=8 shiftwidth=8 expandtab +# $Id: setup.py,v 1.12 2015/10/26 17:03:17 nanard Exp $ +# the MiniUPnP Project (c) 2007-2014 Thomas Bernard +# http://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/ +# +# python script to build the miniupnpc module under unix +# +# replace libminiupnpc.a by libminiupnpc.so for shared library usage +try: + from setuptools import setup, Extension +except ImportError: + from distutils.core import setup, Extension +from distutils import sysconfig +sysconfig.get_config_vars()["OPT"] = '' +sysconfig.get_config_vars()["CFLAGS"] = '' +setup(name="miniupnpc", + version=open('VERSION').read().strip(), + author='Thomas BERNARD', + author_email='miniupnp@free.fr', + license=open('LICENSE').read(), + url='http://miniupnp.free.fr/', + description='miniUPnP client', + ext_modules=[ + Extension(name="miniupnpc", sources=["miniupnpcmodule.c"], + extra_objects=["libminiupnpc.a"]) + ]) + diff --git a/ext/miniupnpc/setupmingw32.py b/ext/miniupnpc/setupmingw32.py new file mode 100644 index 0000000..43dfb46 --- /dev/null +++ b/ext/miniupnpc/setupmingw32.py @@ -0,0 +1,28 @@ +#! /usr/bin/python +# vim: tabstop=8 shiftwidth=8 expandtab +# $Id: setupmingw32.py,v 1.10 2015/10/26 17:03:17 nanard Exp $ +# the MiniUPnP Project (c) 2007-2014 Thomas Bernard +# http://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/ +# +# python script to build the miniupnpc module under windows (using mingw32) +# +try: + from setuptools import setup, Extension +except ImportError: + from distutils.core import setup, Extension +from distutils import sysconfig +sysconfig.get_config_vars()["OPT"] = '' +sysconfig.get_config_vars()["CFLAGS"] = '' +setup(name="miniupnpc", + version=open('VERSION').read().strip(), + author='Thomas BERNARD', + author_email='miniupnp@free.fr', + license=open('LICENSE').read(), + url='http://miniupnp.free.fr/', + description='miniUPnP client', + ext_modules=[ + Extension(name="miniupnpc", sources=["miniupnpcmodule.c"], + libraries=["ws2_32", "iphlpapi"], + extra_objects=["libminiupnpc.a"]) + ]) + diff --git a/ext/miniupnpc/testdesc/linksys_WAG200G_desc.values b/ext/miniupnpc/testdesc/linksys_WAG200G_desc.values new file mode 100644 index 0000000..cf42221 --- /dev/null +++ b/ext/miniupnpc/testdesc/linksys_WAG200G_desc.values @@ -0,0 +1,14 @@ +# values for linksys_WAG200G_desc.xml + +CIF: + servicetype = urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1 + controlurl = /upnp/control/WANCommonIFC1 + eventsuburl = /upnp/event/WANCommonIFC1 + scpdurl = /cmnicfg.xml + +first: + servicetype = urn:schemas-upnp-org:service:WANPPPConnection:1 + controlurl = /upnp/control/WANPPPConn1 + eventsuburl = /upnp/event/WANPPPConn1 + scpdurl = /pppcfg.xml + diff --git a/ext/miniupnpc/testdesc/linksys_WAG200G_desc.xml b/ext/miniupnpc/testdesc/linksys_WAG200G_desc.xml new file mode 100644 index 0000000..d428d73 --- /dev/null +++ b/ext/miniupnpc/testdesc/linksys_WAG200G_desc.xml @@ -0,0 +1,110 @@ + + + +1 +0 + +http://192.168.1.1:49152 + +urn:schemas-upnp-org:device:InternetGatewayDevice:1 +LINKSYS WAG200G Gateway +LINKSYS +http://www.linksys.com +LINKSYS WAG200G Gateway +Wireless-G ADSL Home Gateway +WAG200G +http://www.linksys.com +123456789 +uuid:8ca2eb37-1dd2-11b2-86f1-001a709b5aa8 +WAG200G + + +urn:schemas-upnp-org:service:Layer3Forwarding:1 +urn:upnp-org:serviceId:L3Forwarding1 +/upnp/control/L3Forwarding1 +/upnp/event/L3Forwarding1 +/l3frwd.xml + + + + +urn:schemas-upnp-org:device:WANDevice:1 +WANDevice +LINKSYS +http://www.linksys.com/ +Residential Gateway +Internet Connection Sharing +1 +http://www.linksys.com/ +0000001 +uuid:8ca2eb36-1dd2-11b2-86f1-001a709b5aa8 +WAG200G + + +urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1 +urn:upnp-org:serviceId:WANCommonIFC1 +/upnp/control/WANCommonIFC1 +/upnp/event/WANCommonIFC1 +/cmnicfg.xml + + + + +urn:schemas-upnp-org:device:WANConnectionDevice:1 +WANConnectionDevice +LINKSYS +http://www.linksys.com/ +Residential Gateway +Internet Connection Sharing +1 +http://www.linksys.com/ +0000001 +uuid:8ca2eb37-1dd2-11b2-86f0-001a709b5aa8 +WAG200G + + +urn:schemas-upnp-org:service:WANEthernetLinkConfig:1 +urn:upnp-org:serviceId:WANEthLinkC1 +/upnp/control/WANEthLinkC1 +/upnp/event/WANEthLinkC1 +/wanelcfg.xml + + +urn:schemas-upnp-org:service:WANPPPConnection:1 +urn:upnp-org:serviceId:WANPPPConn1 +/upnp/control/WANPPPConn1 +/upnp/event/WANPPPConn1 +/pppcfg.xml + + + + + + +urn:schemas-upnp-org:device:LANDevice:1 +LANDevice +LINKSYS +http://www.linksys.com/ +Residential Gateway +Residential Gateway +1 +http://www.linksys.com/ +0000001 +uuid:8ca2eb36-1dd2-11b2-86f0-001a709b5aa +8 +WAG200G + + +urn:schemas-upnp-org:service:LANHostConfigManagement:1 +urn:upnp-org:serviceId:LANHostCfg1 +/upnp/control/LANHostCfg1 +/upnp/event/LANHostCfg1 +/lanhostc.xml + + + + +http://192.168.1.1/index.htm + + + diff --git a/ext/miniupnpc/testdesc/new_LiveBox_desc.values b/ext/miniupnpc/testdesc/new_LiveBox_desc.values new file mode 100644 index 0000000..c55552e --- /dev/null +++ b/ext/miniupnpc/testdesc/new_LiveBox_desc.values @@ -0,0 +1,20 @@ +# values for new_LiveBox_desc.xml + +CIF: + servicetype = urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1 + controlurl = /87895a19/upnp/control/WANCommonIFC1 + eventsuburl = /87895a19/upnp/control/WANCommonIFC1 + scpdurl = /87895a19/gateicfgSCPD.xml + +first: + servicetype = urn:schemas-upnp-org:service:WANPPPConnection:2 + controlurl = /87895a19/upnp/control/WANIPConn1 + eventsuburl = /87895a19/upnp/control/WANIPConn1 + scpdurl = /87895a19/gateconnSCPD_PPP.xml + +IPv6FC: + servicetype = urn:schemas-upnp-org:service:WANIPv6FirewallControl:1 + controlurl = /87895a19/upnp/control/WANIPv6FwCtrl1 + eventsuburl = /87895a19/upnp/control/WANIPv6FwCtrl1 + scpdurl = /87895a19/wanipv6fwctrlSCPD.xml + diff --git a/ext/miniupnpc/testdesc/new_LiveBox_desc.xml b/ext/miniupnpc/testdesc/new_LiveBox_desc.xml new file mode 100644 index 0000000..620eb55 --- /dev/null +++ b/ext/miniupnpc/testdesc/new_LiveBox_desc.xml @@ -0,0 +1,90 @@ + + + + 1 + 0 + + + VEN_0129&DEV_0000&SUBSYS_03&REV_250417 + GenericUmPass + NetworkInfrastructure.Gateway + Network.Gateway + urn:schemas-upnp-org:device:InternetGatewayDevice:2 + Orange Livebox + Sagemcom + http://www.sagemcom.com/ + Residential Livebox,(DSL,WAN Ethernet) + uuid:87895a19-50f9-3736-a87f-115c230155f8 + Sagemcom,fr,SG30_sip-fr-4.28.35.1 + 3 + LK14129DP441489 + http://192.168.1.1 + + + + image/png + 16 + 16 + 8 + /87895a19/ligd.png + + + + + urn:schemas-upnp-org:device:WANDevice:2 + WANDevice + Sagemcom + http://www.sagemcom.com/ + WAN Device on Sagemcom,fr,SG30_sip-fr-4.28.35.1 + Residential Livebox,(DSL,WAN Ethernet) + 3 + http://www.sagemcom.com/ + LK14129DP441489 + http://192.168.1.1 + uuid:e2397374-53d8-3fc6-8306-593ba1a34625 + + + + urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1 + urn:upnp-org:serviceId:WANCommonIFC1 + /87895a19/upnp/control/WANCommonIFC1 + /87895a19/upnp/control/WANCommonIFC1 + /87895a19/gateicfgSCPD.xml + + + + + urn:schemas-upnp-org:device:WANConnectionDevice:2 + WANConnectionDevice + Sagemcom + http://www.sagemcom.com/ + WanConnectionDevice on Sagemcom,fr,SG30_sip-fr-4.28.35.1 + Residential Livebox,(DSL,WAN Ethernet) + 3 + http://www.sagemcom.com/ + LK14129DP441489 + http://192.168.1.1 + uuid:44598a08-288e-32c9-8a4d-d3c008ede331 + + + + urn:schemas-upnp-org:service:WANPPPConnection:2 + urn:upnp-org:serviceId:WANIPConn1 + /87895a19/upnp/control/WANIPConn1 + /87895a19/upnp/control/WANIPConn1 + /87895a19/gateconnSCPD_PPP.xml + + + urn:schemas-upnp-org:service:WANIPv6FirewallControl:1 + urn:upnp-org:serviceId:WANIPv6FwCtrl1 + /87895a19/upnp/control/WANIPv6FwCtrl1 + /87895a19/upnp/control/WANIPv6FwCtrl1 + /87895a19/wanipv6fwctrlSCPD.xml + + + + + + + + \ No newline at end of file diff --git a/ext/miniupnpc/testigddescparse.c b/ext/miniupnpc/testigddescparse.c new file mode 100644 index 0000000..c1907fd --- /dev/null +++ b/ext/miniupnpc/testigddescparse.c @@ -0,0 +1,187 @@ +/* $Id: testigddescparse.c,v 1.10 2015/08/06 09:55:24 nanard Exp $ */ +/* Project : miniupnp + * http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2008-2015 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#include +#include +#include +#include "igd_desc_parse.h" +#include "minixml.h" +#include "miniupnpc.h" + +/* count number of differences */ +int compare_service(struct IGDdatas_service * s, FILE * f) +{ + int n = 0; + char line[1024]; + + while(fgets(line, sizeof(line), f)) { + char * value; + char * equal; + char * name; + char * parsedvalue; + int l; + l = strlen(line); + while((l > 0) && ((line[l-1] == '\r') || (line[l-1] == '\n') || (line[l-1] == ' '))) + line[--l] = '\0'; + if(l == 0) + break; /* end on blank line */ + if(line[0] == '#') + continue; /* skip comments */ + equal = strchr(line, '='); + if(equal == NULL) { + fprintf(stderr, "Warning, line does not contain '=' : %s\n", line); + continue; + } + *equal = '\0'; + name = line; + while(*name == ' ' || *name == '\t') + name++; + l = strlen(name); + while((l > 0) && (name[l-1] == ' ' || name[l-1] == '\t')) + name[--l] = '\0'; + value = equal + 1; + while(*value == ' ' || *value == '\t') + value++; + if(strcmp(name, "controlurl") == 0) + parsedvalue = s->controlurl; + else if(strcmp(name, "eventsuburl") == 0) + parsedvalue = s->eventsuburl; + else if(strcmp(name, "scpdurl") == 0) + parsedvalue = s->scpdurl; + else if(strcmp(name, "servicetype") == 0) + parsedvalue = s->servicetype; + else { + fprintf(stderr, "unknown field '%s'\n", name); + continue; + } + if(0 != strcmp(parsedvalue, value)) { + fprintf(stderr, "difference : '%s' != '%s'\n", parsedvalue, value); + n++; + } + } + return n; +} + +int compare_igd(struct IGDdatas * p, FILE * f) +{ + int n = 0; + char line[1024]; + struct IGDdatas_service * s; + + while(fgets(line, sizeof(line), f)) { + char * colon; + int l = (int)strlen(line); + while((l > 0) && (line[l-1] == '\r' || (line[l-1] == '\n'))) + line[--l] = '\0'; + if(l == 0 || line[0] == '#') + continue; /* skip blank lines and comments */ + colon = strchr(line, ':'); + if(colon == NULL) { + fprintf(stderr, "Warning, no ':' : %s\n", line); + continue; + } + s = NULL; + *colon = '\0'; + if(strcmp(line, "CIF") == 0) + s = &p->CIF; + else if(strcmp(line, "first") == 0) + s = &p->first; + else if(strcmp(line, "second") == 0) + s = &p->second; + else if(strcmp(line, "IPv6FC") == 0) + s = &p->IPv6FC; + else { + s = NULL; + fprintf(stderr, "*** unknown service '%s' ***\n", line); + n++; + continue; + } + n += compare_service(s, f); + } + if(n > 0) + fprintf(stderr, "*** %d difference%s ***\n", n, (n > 1) ? "s" : ""); + return n; +} + +int test_igd_desc_parse(char * buffer, int len, FILE * f) +{ + int n; + struct IGDdatas igd; + struct xmlparser parser; + struct UPNPUrls urls; + + memset(&igd, 0, sizeof(struct IGDdatas)); + memset(&parser, 0, sizeof(struct xmlparser)); + parser.xmlstart = buffer; + parser.xmlsize = len; + parser.data = &igd; + parser.starteltfunc = IGDstartelt; + parser.endeltfunc = IGDendelt; + parser.datafunc = IGDdata; + parsexml(&parser); +#ifdef DEBUG + printIGD(&igd); +#endif /* DEBUG */ + GetUPNPUrls(&urls, &igd, "http://fake/desc/url/file.xml", 0); + printf("ipcondescURL='%s'\n", urls.ipcondescURL); + printf("controlURL='%s'\n", urls.controlURL); + printf("controlURL_CIF='%s'\n", urls.controlURL_CIF); + n = f ? compare_igd(&igd, f) : 0; + FreeUPNPUrls(&urls); + return n; +} + +int main(int argc, char * * argv) +{ + FILE * f; + char * buffer; + int len; + int r; + if(argc<2) { + fprintf(stderr, "Usage: %s file.xml [file.values]\n", argv[0]); + return 1; + } + f = fopen(argv[1], "r"); + if(!f) { + fprintf(stderr, "Cannot open %s for reading.\n", argv[1]); + return 1; + } + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + buffer = malloc(len); + if(!buffer) { + fprintf(stderr, "Memory allocation error.\n"); + fclose(f); + return 1; + } + r = (int)fread(buffer, 1, len, f); + if(r != len) { + fprintf(stderr, "Failed to read file %s. %d out of %d bytes.\n", + argv[1], r, len); + fclose(f); + free(buffer); + return 1; + } + fclose(f); + f = NULL; + if(argc > 2) { + f = fopen(argv[2], "r"); + if(!f) { + fprintf(stderr, "Cannot open %s for reading.\n", argv[2]); + free(buffer); + return 1; + } + } + r = test_igd_desc_parse(buffer, len, f); + free(buffer); + if(f) + fclose(f); + return r; +} + diff --git a/ext/miniupnpc/testminiwget.c b/ext/miniupnpc/testminiwget.c new file mode 100644 index 0000000..5eb49ec --- /dev/null +++ b/ext/miniupnpc/testminiwget.c @@ -0,0 +1,55 @@ +/* $Id: testminiwget.c,v 1.5 2016/01/24 17:24:36 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2016 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#include +#include +#include "miniwget.h" + +/** + * This program uses the miniwget / miniwget_getaddr function + * from miniwget.c in order to retreive a web ressource using + * a GET HTTP method, and store it in a file. + */ +int main(int argc, char * * argv) +{ + void * data; + int size, writtensize; + FILE *f; + char addr[64]; + int status_code = -1; + + if(argc < 3) { + fprintf(stderr, "Usage:\t%s url file\n", argv[0]); + fprintf(stderr, "Example:\t%s http://www.google.com/ out.html\n", argv[0]); + return 1; + } + data = miniwget_getaddr(argv[1], &size, addr, sizeof(addr), 0, &status_code); + if(!data || (status_code != 200)) { + if(data) free(data); + fprintf(stderr, "Error %d fetching %s\n", status_code, argv[1]); + return 1; + } + printf("local address : %s\n", addr); + printf("got %d bytes\n", size); + f = fopen(argv[2], "wb"); + if(!f) { + fprintf(stderr, "Cannot open file %s for writing\n", argv[2]); + free(data); + return 1; + } + writtensize = fwrite(data, 1, size, f); + if(writtensize != size) { + fprintf(stderr, "Could only write %d bytes out of %d to %s\n", + writtensize, size, argv[2]); + } else { + printf("%d bytes written to %s\n", writtensize, argv[2]); + } + fclose(f); + free(data); + return 0; +} + diff --git a/ext/miniupnpc/testminiwget.sh b/ext/miniupnpc/testminiwget.sh new file mode 100755 index 0000000..690b405 --- /dev/null +++ b/ext/miniupnpc/testminiwget.sh @@ -0,0 +1,96 @@ +#!/bin/sh +# $Id: testminiwget.sh,v 1.13 2015/09/03 17:57:44 nanard Exp $ +# project miniupnp : http://miniupnp.free.fr/ +# (c) 2011-2015 Thomas Bernard +# +# test program for miniwget.c +# is usually invoked by "make check" +# +# This test program : +# 1 - launches a local HTTP server (minihttptestserver) +# 2 - uses testminiwget to retreive data from this server +# 3 - compares served and received data +# 4 - kills the local HTTP server and exits +# +# The script was tested and works with ksh, bash +# it should now also run with dash + +TMPD=`mktemp -d -t miniwgetXXXXXXXXXX` +HTTPSERVEROUT="${TMPD}/httpserverout" +EXPECTEDFILE="${TMPD}/expectedfile" +DOWNLOADEDFILE="${TMPD}/downloadedfile" +PORT= +RET=0 + +case "$HAVE_IPV6" in + n|no|0) + ADDR=localhost + SERVERARGS="" + ;; + *) + ADDR="[::1]" + SERVERARGS="-6" + ;; + +esac + +#make minihttptestserver +#make testminiwget + +# launching the test HTTP server +./minihttptestserver $SERVERARGS -e $EXPECTEDFILE > $HTTPSERVEROUT & +SERVERPID=$! +while [ -z "$PORT" ]; do + sleep 1 + PORT=`cat $HTTPSERVEROUT | sed 's/Listening on port \([0-9]*\)/\1/' ` +done +echo "Test HTTP server is listening on $PORT" + +URL1="http://$ADDR:$PORT/index.html" +URL2="http://$ADDR:$PORT/chunked" +URL3="http://$ADDR:$PORT/addcrap" + +echo "standard test ..." +./testminiwget $URL1 "${DOWNLOADEDFILE}.1" +if cmp $EXPECTEDFILE "${DOWNLOADEDFILE}.1" ; then + echo "ok" +else + echo "standard test FAILED" + RET=1 +fi + +echo "chunked transfert encoding test ..." +./testminiwget $URL2 "${DOWNLOADEDFILE}.2" +if cmp $EXPECTEDFILE "${DOWNLOADEDFILE}.2" ; then + echo "ok" +else + echo "chunked transfert encoding test FAILED" + RET=1 +fi + +echo "response too long test ..." +./testminiwget $URL3 "${DOWNLOADEDFILE}.3" +if cmp $EXPECTEDFILE "${DOWNLOADEDFILE}.3" ; then + echo "ok" +else + echo "response too long test FAILED" + RET=1 +fi + +# kill the test HTTP server +kill $SERVERPID +wait $SERVERPID + +# remove temporary files (for success cases) +if [ $RET -eq 0 ]; then + rm -f "${DOWNLOADEDFILE}.1" + rm -f "${DOWNLOADEDFILE}.2" + rm -f "${DOWNLOADEDFILE}.3" + rm -f $EXPECTEDFILE $HTTPSERVEROUT + rmdir ${TMPD} +else + echo "at least one of the test FAILED" + echo "directory ${TMPD} is left intact" +fi +exit $RET + diff --git a/ext/miniupnpc/testminixml.c b/ext/miniupnpc/testminixml.c new file mode 100644 index 0000000..57c4a85 --- /dev/null +++ b/ext/miniupnpc/testminixml.c @@ -0,0 +1,89 @@ +/* $Id: testminixml.c,v 1.10 2014/11/17 17:19:13 nanard Exp $ + * MiniUPnP project + * Website : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author : Thomas Bernard. + * Copyright (c) 2005-2014 Thomas Bernard + * + * testminixml.c + * test program for the "minixml" functions. + */ +#include +#include +#include +#include "minixml.h" +#include "igd_desc_parse.h" + +/* ---------------------------------------------------------------------- */ +void printeltname1(void * d, const char * name, int l) +{ + int i; + (void)d; + printf("element "); + for(i=0;i +#include +#include "portlistingparse.h" + +struct port_mapping { + unsigned int leasetime; + unsigned short externalport; + unsigned short internalport; + const char * remotehost; + const char * client; + const char * proto; + const char * desc; + unsigned char enabled; +}; + +/* return the number of differences */ +int test(const char * portListingXml, int portListingXmlLen, + const struct port_mapping * ref, int count) +{ + int i; + int r = 0; + struct PortMappingParserData data; + struct PortMapping * pm; + + memset(&data, 0, sizeof(data)); + ParsePortListing(portListingXml, portListingXmlLen, &data); + for(i = 0, pm = data.l_head; + (pm != NULL) && (i < count); + i++, pm = pm->l_next) { + printf("%2d %s %5hu->%s:%-5hu '%s' '%s' %u\n", + i, pm->protocol, pm->externalPort, pm->internalClient, + pm->internalPort, + pm->description, pm->remoteHost, + (unsigned)pm->leaseTime); + if(0 != strcmp(pm->protocol, ref[i].proto)) { + printf("protocol : '%s' != '%s'\n", pm->protocol, ref[i].proto); + r++; + } + if(pm->externalPort != ref[i].externalport) { + printf("externalPort : %hu != %hu\n", + pm->externalPort, ref[i].externalport); + r++; + } + if(0 != strcmp(pm->internalClient, ref[i].client)) { + printf("client : '%s' != '%s'\n", + pm->internalClient, ref[i].client); + r++; + } + if(pm->internalPort != ref[i].internalport) { + printf("internalPort : %hu != %hu\n", + pm->internalPort, ref[i].internalport); + r++; + } + if(0 != strcmp(pm->description, ref[i].desc)) { + printf("description : '%s' != '%s'\n", + pm->description, ref[i].desc); + r++; + } + if(0 != strcmp(pm->remoteHost, ref[i].remotehost)) { + printf("remoteHost : '%s' != '%s'\n", + pm->remoteHost, ref[i].remotehost); + r++; + } + if((unsigned)pm->leaseTime != ref[i].leasetime) { + printf("leaseTime : %u != %u\n", + (unsigned)pm->leaseTime, ref[i].leasetime); + r++; + } + if(pm->enabled != ref[i].enabled) { + printf("enabled : %d != %d\n", + (int)pm->enabled, (int)ref[i].enabled); + r++; + } + } + if((i != count) || (pm != NULL)) { + printf("count mismatch : i=%d count=%d pm=%p\n", i, count, pm); + r++; + } + FreePortListing(&data); + return r; +} + +const char test_document[] = +"\n" +"\n" +" \n" +" \n" +" 5002\n" +" UDP\n" +" 4001\n" +" 192.168.1.123\n" +" 1\n" +" xxx\n" +" 0\n" +" \n" +" \n" +" 202.233.2.1\n" +" 2345\n" +" TCP\n" +" 2349\n" +" 192.168.1.137\n" +" 1\n" +" dooom\n" +" 346\n" +" \n" +" \n" +" 134.231.2.11\n" +" 12345\n" +" TCP\n" +" 12345\n" +" 192.168.1.137\n" +" 1\n" +" dooom A\n" +" 347\n" +" \n" +""; + +#define PORT_MAPPINGS_COUNT 3 +const struct port_mapping port_mappings[PORT_MAPPINGS_COUNT] = { +{347, 12345, 12345, "134.231.2.11", "192.168.1.137", "TCP", "dooom A", 1}, +{346, 2345, 2349, "202.233.2.1", "192.168.1.137", "TCP", "dooom", 1}, +{0, 5002, 4001, "", "192.168.1.123", "UDP", "xxx", 1} +}; + +/* --- main --- */ +int main(void) +{ + int r; + r = test(test_document, sizeof(test_document) - 1, + port_mappings, PORT_MAPPINGS_COUNT); + if(r == 0) { + printf("test of portlistingparse OK\n"); + return 0; + } else { + printf("test FAILED (%d differences counted)\n", r); + return 1; + } +} + diff --git a/ext/miniupnpc/testreplyparse/DeletePortMapping.namevalue b/ext/miniupnpc/testreplyparse/DeletePortMapping.namevalue new file mode 100644 index 0000000..48ca0cc --- /dev/null +++ b/ext/miniupnpc/testreplyparse/DeletePortMapping.namevalue @@ -0,0 +1,3 @@ +NewRemoteHost= +NewExternalPort=123 +NewProtocol=TCP diff --git a/ext/miniupnpc/testreplyparse/DeletePortMapping.xml b/ext/miniupnpc/testreplyparse/DeletePortMapping.xml new file mode 100644 index 0000000..a955c53 --- /dev/null +++ b/ext/miniupnpc/testreplyparse/DeletePortMapping.xml @@ -0,0 +1,6 @@ + +123 +TCP + + + diff --git a/ext/miniupnpc/testreplyparse/GetExternalIPAddress.namevalue b/ext/miniupnpc/testreplyparse/GetExternalIPAddress.namevalue new file mode 100644 index 0000000..5aa75f8 --- /dev/null +++ b/ext/miniupnpc/testreplyparse/GetExternalIPAddress.namevalue @@ -0,0 +1,2 @@ +NewExternalIPAddress=1.2.3.4 + diff --git a/ext/miniupnpc/testreplyparse/GetExternalIPAddress.xml b/ext/miniupnpc/testreplyparse/GetExternalIPAddress.xml new file mode 100644 index 0000000..db7ec1f --- /dev/null +++ b/ext/miniupnpc/testreplyparse/GetExternalIPAddress.xml @@ -0,0 +1,2 @@ +1.2.3.4 + diff --git a/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.namevalue b/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.namevalue new file mode 100644 index 0000000..26b169c --- /dev/null +++ b/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.namevalue @@ -0,0 +1,3 @@ +NewProtocol=UDP +NewExternalPort=12345 +NewRemoteHost= diff --git a/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.xml b/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.xml new file mode 100644 index 0000000..bbb540e --- /dev/null +++ b/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryReq.xml @@ -0,0 +1,3 @@ + +12345UDP + diff --git a/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.namevalue b/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.namevalue new file mode 100644 index 0000000..2189789 --- /dev/null +++ b/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.namevalue @@ -0,0 +1,5 @@ +NewInternalPort=12345 +NewInternalClient=192.168.10.110 +NewEnabled=1 +NewPortMappingDescription=libminiupnpc +NewLeaseDuration=0 diff --git a/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.xml b/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.xml new file mode 100644 index 0000000..77e8d9c --- /dev/null +++ b/ext/miniupnpc/testreplyparse/GetSpecificPortMappingEntryResp.xml @@ -0,0 +1,2 @@ +12345192.168.10.1101libminiupnpc0 + diff --git a/ext/miniupnpc/testreplyparse/SetDefaultConnectionService.namevalue b/ext/miniupnpc/testreplyparse/SetDefaultConnectionService.namevalue new file mode 100644 index 0000000..f78c7e2 --- /dev/null +++ b/ext/miniupnpc/testreplyparse/SetDefaultConnectionService.namevalue @@ -0,0 +1 @@ +NewDefaultConnectionService=uuid:c6c05a33-f704-48df-9910-e099b3471d81:WANConnectionDevice:1,INVALID_SERVICE_ID diff --git a/ext/miniupnpc/testreplyparse/SetDefaultConnectionService.xml b/ext/miniupnpc/testreplyparse/SetDefaultConnectionService.xml new file mode 100644 index 0000000..ac04c07 --- /dev/null +++ b/ext/miniupnpc/testreplyparse/SetDefaultConnectionService.xml @@ -0,0 +1 @@ +uuid:c6c05a33-f704-48df-9910-e099b3471d81:WANConnectionDevice:1,INVALID_SERVICE_ID diff --git a/ext/miniupnpc/testreplyparse/readme.txt b/ext/miniupnpc/testreplyparse/readme.txt new file mode 100644 index 0000000..3eb1f01 --- /dev/null +++ b/ext/miniupnpc/testreplyparse/readme.txt @@ -0,0 +1,7 @@ +This directory contains files used for validation of upnpreplyparse.c code. + +Each .xml file to parse should give the results which are in the .namevalue +file. + +A .namevalue file contain name=value lines. + diff --git a/ext/miniupnpc/testupnpigd.py b/ext/miniupnpc/testupnpigd.py new file mode 100755 index 0000000..6d167a4 --- /dev/null +++ b/ext/miniupnpc/testupnpigd.py @@ -0,0 +1,84 @@ +#! /usr/bin/python +# $Id: testupnpigd.py,v 1.4 2008/10/11 10:27:20 nanard Exp $ +# MiniUPnP project +# Author : Thomas Bernard +# This Sample code is public domain. +# website : http://miniupnp.tuxfamily.org/ + +# import the python miniupnpc module +import miniupnpc +import socket +import BaseHTTPServer + +# function definition +def list_redirections(): + i = 0 + while True: + p = u.getgenericportmapping(i) + if p==None: + break + print i, p + i = i + 1 + +#define the handler class for HTTP connections +class handler_class(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.end_headers() + self.wfile.write("OK MON GARS") + +# create the object +u = miniupnpc.UPnP() +#print 'inital(default) values :' +#print ' discoverdelay', u.discoverdelay +#print ' lanaddr', u.lanaddr +#print ' multicastif', u.multicastif +#print ' minissdpdsocket', u.minissdpdsocket +u.discoverdelay = 200; + +try: + print 'Discovering... delay=%ums' % u.discoverdelay + ndevices = u.discover() + print ndevices, 'device(s) detected' + + # select an igd + u.selectigd() + # display information about the IGD and the internet connection + print 'local ip address :', u.lanaddr + externalipaddress = u.externalipaddress() + print 'external ip address :', externalipaddress + print u.statusinfo(), u.connectiontype() + + #instanciate a HTTPd object. The port is assigned by the system. + httpd = BaseHTTPServer.HTTPServer((u.lanaddr, 0), handler_class) + eport = httpd.server_port + + # find a free port for the redirection + r = u.getspecificportmapping(eport, 'TCP') + while r != None and eport < 65536: + eport = eport + 1 + r = u.getspecificportmapping(eport, 'TCP') + + print 'trying to redirect %s port %u TCP => %s port %u TCP' % (externalipaddress, eport, u.lanaddr, httpd.server_port) + + b = u.addportmapping(eport, 'TCP', u.lanaddr, httpd.server_port, + 'UPnP IGD Tester port %u' % eport, '') + if b: + print 'Success. Now waiting for some HTTP request on http://%s:%u' % (externalipaddress ,eport) + try: + httpd.handle_request() + httpd.server_close() + except KeyboardInterrupt, details: + print "CTRL-C exception!", details + b = u.deleteportmapping(eport, 'TCP') + if b: + print 'Successfully deleted port mapping' + else: + print 'Failed to remove port mapping' + else: + print 'Failed' + + httpd.server_close() + +except Exception, e: + print 'Exception :', e diff --git a/ext/miniupnpc/testupnpreplyparse.c b/ext/miniupnpc/testupnpreplyparse.c new file mode 100644 index 0000000..7ba7131 --- /dev/null +++ b/ext/miniupnpc/testupnpreplyparse.c @@ -0,0 +1,96 @@ +/* $Id: testupnpreplyparse.c,v 1.4 2014/01/27 11:45:19 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2014 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ +#include +#include +#include +#include "upnpreplyparse.h" + +int +test_parsing(const char * buf, int len, FILE * f) +{ + char line[1024]; + struct NameValueParserData pdata; + int ok = 1; + ParseNameValue(buf, len, &pdata); + /* check result */ + if(f != NULL) + { + while(fgets(line, sizeof(line), f)) + { + char * value; + char * equal; + char * parsedvalue; + int l; + l = strlen(line); + while((l > 0) && ((line[l-1] == '\r') || (line[l-1] == '\n'))) + line[--l] = '\0'; + /* skip empty lines */ + if(l == 0) + continue; + equal = strchr(line, '='); + if(equal == NULL) + { + fprintf(stderr, "Warning, line does not contain '=' : %s\n", line); + continue; + } + *equal = '\0'; + value = equal + 1; + parsedvalue = GetValueFromNameValueList(&pdata, line); + if((parsedvalue == NULL) || (strcmp(parsedvalue, value) != 0)) + { + fprintf(stderr, "Element <%s> : expecting value '%s', got '%s'\n", + line, value, parsedvalue ? parsedvalue : ""); + ok = 0; + } + } + } + ClearNameValueList(&pdata); + return ok; +} + +int main(int argc, char * * argv) +{ + FILE * f; + char buffer[4096]; + int l; + int ok; + + if(argc<2) + { + fprintf(stderr, "Usage: %s file.xml [file.namevalues]\n", argv[0]); + return 1; + } + f = fopen(argv[1], "r"); + if(!f) + { + fprintf(stderr, "Error : can not open file %s\n", argv[1]); + return 2; + } + l = fread(buffer, 1, sizeof(buffer)-1, f); + fclose(f); + f = NULL; + buffer[l] = '\0'; + if(argc > 2) + { + f = fopen(argv[2], "r"); + if(!f) + { + fprintf(stderr, "Error : can not open file %s\n", argv[2]); + return 2; + } + } +#ifdef DEBUG + DisplayNameValueList(buffer, l); +#endif + ok = test_parsing(buffer, l, f); + if(f) + { + fclose(f); + } + return ok ? 0 : 3; +} + diff --git a/ext/miniupnpc/testupnpreplyparse.sh b/ext/miniupnpc/testupnpreplyparse.sh new file mode 100755 index 0000000..992930b --- /dev/null +++ b/ext/miniupnpc/testupnpreplyparse.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +for f in testreplyparse/*.xml ; do + bf="`dirname $f`/`basename $f .xml`" + if ./testupnpreplyparse $f $bf.namevalue ; then + echo "$f : passed" + else + echo "$f : FAILED" + exit 1 + fi +done + +exit 0 + diff --git a/ext/miniupnpc/updateminiupnpcstrings.sh b/ext/miniupnpc/updateminiupnpcstrings.sh new file mode 100755 index 0000000..dde4354 --- /dev/null +++ b/ext/miniupnpc/updateminiupnpcstrings.sh @@ -0,0 +1,53 @@ +#! /bin/sh +# $Id: updateminiupnpcstrings.sh,v 1.7 2011/01/04 11:41:53 nanard Exp $ +# project miniupnp : http://miniupnp.free.fr/ +# (c) 2009 Thomas Bernard + +FILE=miniupnpcstrings.h +TMPFILE=miniupnpcstrings.h.tmp +TEMPLATE_FILE=${FILE}.in + +# detecting the OS name and version +OS_NAME=`uname -s` +OS_VERSION=`uname -r` +if [ -f /etc/debian_version ]; then + OS_NAME=Debian + OS_VERSION=`cat /etc/debian_version` +fi +# use lsb_release (Linux Standard Base) when available +LSB_RELEASE=`which lsb_release` +if [ 0 -eq $? -a -x "${LSB_RELEASE}" ]; then + OS_NAME=`${LSB_RELEASE} -i -s` + OS_VERSION=`${LSB_RELEASE} -r -s` + case $OS_NAME in + Debian) + #OS_VERSION=`${LSB_RELEASE} -c -s` + ;; + Ubuntu) + #OS_VERSION=`${LSB_RELEASE} -c -s` + ;; + esac +fi + +# on AmigaOS 3, uname -r returns "unknown", so we use uname -v +if [ "$OS_NAME" = "AmigaOS" ]; then + if [ "$OS_VERSION" = "unknown" ]; then + OS_VERSION=`uname -v` + fi +fi + +echo "Detected OS [$OS_NAME] version [$OS_VERSION]" +MINIUPNPC_VERSION=`cat VERSION` +echo "MiniUPnPc version [${MINIUPNPC_VERSION}]" + +EXPR="s|OS_STRING \".*\"|OS_STRING \"${OS_NAME}/${OS_VERSION}\"|" +#echo $EXPR +test -f ${FILE}.in +echo "setting OS_STRING macro value to ${OS_NAME}/${OS_VERSION} in $FILE." +sed -e "$EXPR" < $TEMPLATE_FILE > $TMPFILE + +EXPR="s|MINIUPNPC_VERSION_STRING \".*\"|MINIUPNPC_VERSION_STRING \"${MINIUPNPC_VERSION}\"|" +echo "setting MINIUPNPC_VERSION_STRING macro value to ${MINIUPNPC_VERSION} in $FILE." +sed -e "$EXPR" < $TMPFILE > $FILE +rm $TMPFILE + diff --git a/ext/miniupnpc/upnpc.c b/ext/miniupnpc/upnpc.c new file mode 100644 index 0000000..8e7edad --- /dev/null +++ b/ext/miniupnpc/upnpc.c @@ -0,0 +1,855 @@ +/* $Id: upnpc.c,v 1.115 2016/10/07 09:04:01 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2016 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#define snprintf _snprintf +#else +/* for IPPROTO_TCP / IPPROTO_UDP */ +#include +#endif +#include +#include "miniwget.h" +#include "miniupnpc.h" +#include "upnpcommands.h" +#include "upnperrors.h" +#include "miniupnpcstrings.h" + +/* protofix() checks if protocol is "UDP" or "TCP" + * returns NULL if not */ +const char * protofix(const char * proto) +{ + static const char proto_tcp[4] = { 'T', 'C', 'P', 0}; + static const char proto_udp[4] = { 'U', 'D', 'P', 0}; + int i, b; + for(i=0, b=1; i<4; i++) + b = b && ( (proto[i] == proto_tcp[i]) + || (proto[i] == (proto_tcp[i] | 32)) ); + if(b) + return proto_tcp; + for(i=0, b=1; i<4; i++) + b = b && ( (proto[i] == proto_udp[i]) + || (proto[i] == (proto_udp[i] | 32)) ); + if(b) + return proto_udp; + return 0; +} + +/* is_int() checks if parameter is an integer or not + * 1 for integer + * 0 for not an integer */ +int is_int(char const* s) +{ + if(s == NULL) + return 0; + while(*s) { + /* #define isdigit(c) ((c) >= '0' && (c) <= '9') */ + if(!isdigit(*s)) + return 0; + s++; + } + return 1; +} + +static void DisplayInfos(struct UPNPUrls * urls, + struct IGDdatas * data) +{ + char externalIPAddress[40]; + char connectionType[64]; + char status[64]; + char lastconnerr[64]; + unsigned int uptime = 0; + unsigned int brUp, brDown; + time_t timenow, timestarted; + int r; + if(UPNP_GetConnectionTypeInfo(urls->controlURL, + data->first.servicetype, + connectionType) != UPNPCOMMAND_SUCCESS) + printf("GetConnectionTypeInfo failed.\n"); + else + printf("Connection Type : %s\n", connectionType); + if(UPNP_GetStatusInfo(urls->controlURL, data->first.servicetype, + status, &uptime, lastconnerr) != UPNPCOMMAND_SUCCESS) + printf("GetStatusInfo failed.\n"); + else + printf("Status : %s, uptime=%us, LastConnectionError : %s\n", + status, uptime, lastconnerr); + if(uptime > 0) { + timenow = time(NULL); + timestarted = timenow - uptime; + printf(" Time started : %s", ctime(×tarted)); + } + if(UPNP_GetLinkLayerMaxBitRates(urls->controlURL_CIF, data->CIF.servicetype, + &brDown, &brUp) != UPNPCOMMAND_SUCCESS) { + printf("GetLinkLayerMaxBitRates failed.\n"); + } else { + printf("MaxBitRateDown : %u bps", brDown); + if(brDown >= 1000000) { + printf(" (%u.%u Mbps)", brDown / 1000000, (brDown / 100000) % 10); + } else if(brDown >= 1000) { + printf(" (%u Kbps)", brDown / 1000); + } + printf(" MaxBitRateUp %u bps", brUp); + if(brUp >= 1000000) { + printf(" (%u.%u Mbps)", brUp / 1000000, (brUp / 100000) % 10); + } else if(brUp >= 1000) { + printf(" (%u Kbps)", brUp / 1000); + } + printf("\n"); + } + r = UPNP_GetExternalIPAddress(urls->controlURL, + data->first.servicetype, + externalIPAddress); + if(r != UPNPCOMMAND_SUCCESS) { + printf("GetExternalIPAddress failed. (errorcode=%d)\n", r); + } else { + printf("ExternalIPAddress = %s\n", externalIPAddress); + } +} + +static void GetConnectionStatus(struct UPNPUrls * urls, + struct IGDdatas * data) +{ + unsigned int bytessent, bytesreceived, packetsreceived, packetssent; + DisplayInfos(urls, data); + bytessent = UPNP_GetTotalBytesSent(urls->controlURL_CIF, data->CIF.servicetype); + bytesreceived = UPNP_GetTotalBytesReceived(urls->controlURL_CIF, data->CIF.servicetype); + packetssent = UPNP_GetTotalPacketsSent(urls->controlURL_CIF, data->CIF.servicetype); + packetsreceived = UPNP_GetTotalPacketsReceived(urls->controlURL_CIF, data->CIF.servicetype); + printf("Bytes: Sent: %8u\tRecv: %8u\n", bytessent, bytesreceived); + printf("Packets: Sent: %8u\tRecv: %8u\n", packetssent, packetsreceived); +} + +static void ListRedirections(struct UPNPUrls * urls, + struct IGDdatas * data) +{ + int r; + int i = 0; + char index[6]; + char intClient[40]; + char intPort[6]; + char extPort[6]; + char protocol[4]; + char desc[80]; + char enabled[6]; + char rHost[64]; + char duration[16]; + /*unsigned int num=0; + UPNP_GetPortMappingNumberOfEntries(urls->controlURL, data->servicetype, &num); + printf("PortMappingNumberOfEntries : %u\n", num);*/ + printf(" i protocol exPort->inAddr:inPort description remoteHost leaseTime\n"); + do { + snprintf(index, 6, "%d", i); + rHost[0] = '\0'; enabled[0] = '\0'; + duration[0] = '\0'; desc[0] = '\0'; + extPort[0] = '\0'; intPort[0] = '\0'; intClient[0] = '\0'; + r = UPNP_GetGenericPortMappingEntry(urls->controlURL, + data->first.servicetype, + index, + extPort, intClient, intPort, + protocol, desc, enabled, + rHost, duration); + if(r==0) + /* + printf("%02d - %s %s->%s:%s\tenabled=%s leaseDuration=%s\n" + " desc='%s' rHost='%s'\n", + i, protocol, extPort, intClient, intPort, + enabled, duration, + desc, rHost); + */ + printf("%2d %s %5s->%s:%-5s '%s' '%s' %s\n", + i, protocol, extPort, intClient, intPort, + desc, rHost, duration); + else + printf("GetGenericPortMappingEntry() returned %d (%s)\n", + r, strupnperror(r)); + i++; + } while(r==0); +} + +static void NewListRedirections(struct UPNPUrls * urls, + struct IGDdatas * data) +{ + int r; + int i = 0; + struct PortMappingParserData pdata; + struct PortMapping * pm; + + memset(&pdata, 0, sizeof(struct PortMappingParserData)); + r = UPNP_GetListOfPortMappings(urls->controlURL, + data->first.servicetype, + "0", + "65535", + "TCP", + "1000", + &pdata); + if(r == UPNPCOMMAND_SUCCESS) + { + printf(" i protocol exPort->inAddr:inPort description remoteHost leaseTime\n"); + for(pm = pdata.l_head; pm != NULL; pm = pm->l_next) + { + printf("%2d %s %5hu->%s:%-5hu '%s' '%s' %u\n", + i, pm->protocol, pm->externalPort, pm->internalClient, + pm->internalPort, + pm->description, pm->remoteHost, + (unsigned)pm->leaseTime); + i++; + } + FreePortListing(&pdata); + } + else + { + printf("GetListOfPortMappings() returned %d (%s)\n", + r, strupnperror(r)); + } + r = UPNP_GetListOfPortMappings(urls->controlURL, + data->first.servicetype, + "0", + "65535", + "UDP", + "1000", + &pdata); + if(r == UPNPCOMMAND_SUCCESS) + { + for(pm = pdata.l_head; pm != NULL; pm = pm->l_next) + { + printf("%2d %s %5hu->%s:%-5hu '%s' '%s' %u\n", + i, pm->protocol, pm->externalPort, pm->internalClient, + pm->internalPort, + pm->description, pm->remoteHost, + (unsigned)pm->leaseTime); + i++; + } + FreePortListing(&pdata); + } + else + { + printf("GetListOfPortMappings() returned %d (%s)\n", + r, strupnperror(r)); + } +} + +/* Test function + * 1 - get connection type + * 2 - get extenal ip address + * 3 - Add port mapping + * 4 - get this port mapping from the IGD */ +static int SetRedirectAndTest(struct UPNPUrls * urls, + struct IGDdatas * data, + const char * iaddr, + const char * iport, + const char * eport, + const char * proto, + const char * leaseDuration, + const char * description, + int addAny) +{ + char externalIPAddress[40]; + char intClient[40]; + char intPort[6]; + char reservedPort[6]; + char duration[16]; + int r; + + if(!iaddr || !iport || !eport || !proto) + { + fprintf(stderr, "Wrong arguments\n"); + return -1; + } + proto = protofix(proto); + if(!proto) + { + fprintf(stderr, "invalid protocol\n"); + return -1; + } + + r = UPNP_GetExternalIPAddress(urls->controlURL, + data->first.servicetype, + externalIPAddress); + if(r!=UPNPCOMMAND_SUCCESS) + printf("GetExternalIPAddress failed.\n"); + else + printf("ExternalIPAddress = %s\n", externalIPAddress); + + if (addAny) { + r = UPNP_AddAnyPortMapping(urls->controlURL, data->first.servicetype, + eport, iport, iaddr, description, + proto, 0, leaseDuration, reservedPort); + if(r==UPNPCOMMAND_SUCCESS) + eport = reservedPort; + else + printf("AddAnyPortMapping(%s, %s, %s) failed with code %d (%s)\n", + eport, iport, iaddr, r, strupnperror(r)); + } else { + r = UPNP_AddPortMapping(urls->controlURL, data->first.servicetype, + eport, iport, iaddr, description, + proto, 0, leaseDuration); + if(r!=UPNPCOMMAND_SUCCESS) + printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", + eport, iport, iaddr, r, strupnperror(r)); + } + + r = UPNP_GetSpecificPortMappingEntry(urls->controlURL, + data->first.servicetype, + eport, proto, NULL/*remoteHost*/, + intClient, intPort, NULL/*desc*/, + NULL/*enabled*/, duration); + if(r!=UPNPCOMMAND_SUCCESS) { + printf("GetSpecificPortMappingEntry() failed with code %d (%s)\n", + r, strupnperror(r)); + return -2; + } else { + printf("InternalIP:Port = %s:%s\n", intClient, intPort); + printf("external %s:%s %s is redirected to internal %s:%s (duration=%s)\n", + externalIPAddress, eport, proto, intClient, intPort, duration); + } + return 0; +} + +static int +RemoveRedirect(struct UPNPUrls * urls, + struct IGDdatas * data, + const char * eport, + const char * proto, + const char * remoteHost) +{ + int r; + if(!proto || !eport) + { + fprintf(stderr, "invalid arguments\n"); + return -1; + } + proto = protofix(proto); + if(!proto) + { + fprintf(stderr, "protocol invalid\n"); + return -1; + } + r = UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, eport, proto, remoteHost); + if(r!=UPNPCOMMAND_SUCCESS) { + printf("UPNP_DeletePortMapping() failed with code : %d\n", r); + return -2; + }else { + printf("UPNP_DeletePortMapping() returned : %d\n", r); + } + return 0; +} + +static int +RemoveRedirectRange(struct UPNPUrls * urls, + struct IGDdatas * data, + const char * ePortStart, char const * ePortEnd, + const char * proto, const char * manage) +{ + int r; + + if (!manage) + manage = "0"; + + if(!proto || !ePortStart || !ePortEnd) + { + fprintf(stderr, "invalid arguments\n"); + return -1; + } + proto = protofix(proto); + if(!proto) + { + fprintf(stderr, "protocol invalid\n"); + return -1; + } + r = UPNP_DeletePortMappingRange(urls->controlURL, data->first.servicetype, ePortStart, ePortEnd, proto, manage); + if(r!=UPNPCOMMAND_SUCCESS) { + printf("UPNP_DeletePortMappingRange() failed with code : %d\n", r); + return -2; + }else { + printf("UPNP_DeletePortMappingRange() returned : %d\n", r); + } + return 0; +} + +/* IGD:2, functions for service WANIPv6FirewallControl:1 */ +static void GetFirewallStatus(struct UPNPUrls * urls, struct IGDdatas * data) +{ + unsigned int bytessent, bytesreceived, packetsreceived, packetssent; + int firewallEnabled = 0, inboundPinholeAllowed = 0; + + UPNP_GetFirewallStatus(urls->controlURL_6FC, data->IPv6FC.servicetype, &firewallEnabled, &inboundPinholeAllowed); + printf("FirewallEnabled: %d & Inbound Pinhole Allowed: %d\n", firewallEnabled, inboundPinholeAllowed); + printf("GetFirewallStatus:\n Firewall Enabled: %s\n Inbound Pinhole Allowed: %s\n", (firewallEnabled)? "Yes":"No", (inboundPinholeAllowed)? "Yes":"No"); + + bytessent = UPNP_GetTotalBytesSent(urls->controlURL_CIF, data->CIF.servicetype); + bytesreceived = UPNP_GetTotalBytesReceived(urls->controlURL_CIF, data->CIF.servicetype); + packetssent = UPNP_GetTotalPacketsSent(urls->controlURL_CIF, data->CIF.servicetype); + packetsreceived = UPNP_GetTotalPacketsReceived(urls->controlURL_CIF, data->CIF.servicetype); + printf("Bytes: Sent: %8u\tRecv: %8u\n", bytessent, bytesreceived); + printf("Packets: Sent: %8u\tRecv: %8u\n", packetssent, packetsreceived); +} + +/* Test function + * 1 - Add pinhole + * 2 - Check if pinhole is working from the IGD side */ +static void SetPinholeAndTest(struct UPNPUrls * urls, struct IGDdatas * data, + const char * remoteaddr, const char * eport, + const char * intaddr, const char * iport, + const char * proto, const char * lease_time) +{ + char uniqueID[8]; + /*int isWorking = 0;*/ + int r; + char proto_tmp[8]; + + if(!intaddr || !remoteaddr || !iport || !eport || !proto || !lease_time) + { + fprintf(stderr, "Wrong arguments\n"); + return; + } + if(atoi(proto) == 0) + { + const char * protocol; + protocol = protofix(proto); + if(protocol && (strcmp("TCP", protocol) == 0)) + { + snprintf(proto_tmp, sizeof(proto_tmp), "%d", IPPROTO_TCP); + proto = proto_tmp; + } + else if(protocol && (strcmp("UDP", protocol) == 0)) + { + snprintf(proto_tmp, sizeof(proto_tmp), "%d", IPPROTO_UDP); + proto = proto_tmp; + } + else + { + fprintf(stderr, "invalid protocol\n"); + return; + } + } + r = UPNP_AddPinhole(urls->controlURL_6FC, data->IPv6FC.servicetype, remoteaddr, eport, intaddr, iport, proto, lease_time, uniqueID); + if(r!=UPNPCOMMAND_SUCCESS) + printf("AddPinhole([%s]:%s -> [%s]:%s) failed with code %d (%s)\n", + remoteaddr, eport, intaddr, iport, r, strupnperror(r)); + else + { + printf("AddPinhole: ([%s]:%s -> [%s]:%s) / Pinhole ID = %s\n", + remoteaddr, eport, intaddr, iport, uniqueID); + /*r = UPNP_CheckPinholeWorking(urls->controlURL_6FC, data->servicetype_6FC, uniqueID, &isWorking); + if(r!=UPNPCOMMAND_SUCCESS) + printf("CheckPinholeWorking() failed with code %d (%s)\n", r, strupnperror(r)); + printf("CheckPinholeWorking: Pinhole ID = %s / IsWorking = %s\n", uniqueID, (isWorking)? "Yes":"No");*/ + } +} + +/* Test function + * 1 - Check if pinhole is working from the IGD side + * 2 - Update pinhole */ +static void GetPinholeAndUpdate(struct UPNPUrls * urls, struct IGDdatas * data, + const char * uniqueID, const char * lease_time) +{ + int isWorking = 0; + int r; + + if(!uniqueID || !lease_time) + { + fprintf(stderr, "Wrong arguments\n"); + return; + } + r = UPNP_CheckPinholeWorking(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, &isWorking); + printf("CheckPinholeWorking: Pinhole ID = %s / IsWorking = %s\n", uniqueID, (isWorking)? "Yes":"No"); + if(r!=UPNPCOMMAND_SUCCESS) + printf("CheckPinholeWorking() failed with code %d (%s)\n", r, strupnperror(r)); + if(isWorking || r==709) + { + r = UPNP_UpdatePinhole(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, lease_time); + printf("UpdatePinhole: Pinhole ID = %s with Lease Time: %s\n", uniqueID, lease_time); + if(r!=UPNPCOMMAND_SUCCESS) + printf("UpdatePinhole: ID (%s) failed with code %d (%s)\n", uniqueID, r, strupnperror(r)); + } +} + +/* Test function + * Get pinhole timeout + */ +static void GetPinholeOutboundTimeout(struct UPNPUrls * urls, struct IGDdatas * data, + const char * remoteaddr, const char * eport, + const char * intaddr, const char * iport, + const char * proto) +{ + int timeout = 0; + int r; + + if(!intaddr || !remoteaddr || !iport || !eport || !proto) + { + fprintf(stderr, "Wrong arguments\n"); + return; + } + + r = UPNP_GetOutboundPinholeTimeout(urls->controlURL_6FC, data->IPv6FC.servicetype, remoteaddr, eport, intaddr, iport, proto, &timeout); + if(r!=UPNPCOMMAND_SUCCESS) + printf("GetOutboundPinholeTimeout([%s]:%s -> [%s]:%s) failed with code %d (%s)\n", + intaddr, iport, remoteaddr, eport, r, strupnperror(r)); + else + printf("GetOutboundPinholeTimeout: ([%s]:%s -> [%s]:%s) / Timeout = %d\n", intaddr, iport, remoteaddr, eport, timeout); +} + +static void +GetPinholePackets(struct UPNPUrls * urls, + struct IGDdatas * data, const char * uniqueID) +{ + int r, pinholePackets = 0; + if(!uniqueID) + { + fprintf(stderr, "invalid arguments\n"); + return; + } + r = UPNP_GetPinholePackets(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, &pinholePackets); + if(r!=UPNPCOMMAND_SUCCESS) + printf("GetPinholePackets() failed with code %d (%s)\n", r, strupnperror(r)); + else + printf("GetPinholePackets: Pinhole ID = %s / PinholePackets = %d\n", uniqueID, pinholePackets); +} + +static void +CheckPinhole(struct UPNPUrls * urls, + struct IGDdatas * data, const char * uniqueID) +{ + int r, isWorking = 0; + if(!uniqueID) + { + fprintf(stderr, "invalid arguments\n"); + return; + } + r = UPNP_CheckPinholeWorking(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, &isWorking); + if(r!=UPNPCOMMAND_SUCCESS) + printf("CheckPinholeWorking() failed with code %d (%s)\n", r, strupnperror(r)); + else + printf("CheckPinholeWorking: Pinhole ID = %s / IsWorking = %s\n", uniqueID, (isWorking)? "Yes":"No"); +} + +static void +RemovePinhole(struct UPNPUrls * urls, + struct IGDdatas * data, const char * uniqueID) +{ + int r; + if(!uniqueID) + { + fprintf(stderr, "invalid arguments\n"); + return; + } + r = UPNP_DeletePinhole(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID); + printf("UPNP_DeletePinhole() returned : %d\n", r); +} + + +/* sample upnp client program */ +int main(int argc, char ** argv) +{ + char command = 0; + char ** commandargv = 0; + int commandargc = 0; + struct UPNPDev * devlist = 0; + char lanaddr[64] = "unset"; /* my ip address on the LAN */ + int i; + const char * rootdescurl = 0; + const char * multicastif = 0; + const char * minissdpdpath = 0; + int localport = UPNP_LOCAL_PORT_ANY; + int retcode = 0; + int error = 0; + int ipv6 = 0; + unsigned char ttl = 2; /* defaulting to 2 */ + const char * description = 0; + +#ifdef _WIN32 + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2,2), &wsaData); + if(nResult != NO_ERROR) + { + fprintf(stderr, "WSAStartup() failed.\n"); + return -1; + } +#endif + printf("upnpc : miniupnpc library test client, version %s.\n", MINIUPNPC_VERSION_STRING); + printf(" (c) 2005-2016 Thomas Bernard.\n"); + printf("Go to http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/\n" + "for more information.\n"); + /* command line processing */ + for(i=1; i65535 || + (localport >1 && localport < 1024)) + { + fprintf(stderr, "Invalid localport '%s'\n", argv[i]); + localport = UPNP_LOCAL_PORT_ANY; + break; + } + } + else if(argv[i][1] == 'p') + minissdpdpath = argv[++i]; + else if(argv[i][1] == '6') + ipv6 = 1; + else if(argv[i][1] == 'e') + description = argv[++i]; + else if(argv[i][1] == 't') + ttl = (unsigned char)atoi(argv[++i]); + else + { + command = argv[i][1]; + i++; + commandargv = argv + i; + commandargc = argc - i; + break; + } + } + else + { + fprintf(stderr, "option '%s' invalid\n", argv[i]); + } + } + + if(!command + || (command == 'a' && commandargc<4) + || (command == 'd' && argc<2) + || (command == 'r' && argc<2) + || (command == 'A' && commandargc<6) + || (command == 'U' && commandargc<2) + || (command == 'D' && commandargc<1)) + { + fprintf(stderr, "Usage :\t%s [options] -a ip port external_port protocol [duration]\n\t\tAdd port redirection\n", argv[0]); + fprintf(stderr, " \t%s [options] -d external_port protocol \n\t\tDelete port redirection\n", argv[0]); + fprintf(stderr, " \t%s [options] -s\n\t\tGet Connection status\n", argv[0]); + fprintf(stderr, " \t%s [options] -l\n\t\tList redirections\n", argv[0]); + fprintf(stderr, " \t%s [options] -L\n\t\tList redirections (using GetListOfPortMappings (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -n ip port external_port protocol [duration]\n\t\tAdd (any) port redirection allowing IGD to use alternative external_port (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -N external_port_start external_port_end protocol [manage]\n\t\tDelete range of port redirections (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -r port1 [external_port1] protocol1 [port2 [external_port2] protocol2] [...]\n\t\tAdd all redirections to the current host\n", argv[0]); + fprintf(stderr, " \t%s [options] -A remote_ip remote_port internal_ip internal_port protocol lease_time\n\t\tAdd Pinhole (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -U uniqueID new_lease_time\n\t\tUpdate Pinhole (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -C uniqueID\n\t\tCheck if Pinhole is Working (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -K uniqueID\n\t\tGet Number of packets going through the rule (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -D uniqueID\n\t\tDelete Pinhole (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -S\n\t\tGet Firewall status (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -G remote_ip remote_port internal_ip internal_port protocol\n\t\tGet Outbound Pinhole Timeout (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -P\n\t\tGet Presentation url\n", argv[0]); + fprintf(stderr, "\nprotocol is UDP or TCP\n"); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -e description : set description for port mapping.\n"); + fprintf(stderr, " -6 : use ip v6 instead of ip v4.\n"); + fprintf(stderr, " -u url : bypass discovery process by providing the XML root description url.\n"); + fprintf(stderr, " -m address/interface : provide ip address (ip v4) or interface name (ip v4 or v6) to use for sending SSDP multicast packets.\n"); + fprintf(stderr, " -z localport : SSDP packets local (source) port (1024-65535).\n"); + fprintf(stderr, " -p path : use this path for MiniSSDPd socket.\n"); + fprintf(stderr, " -t ttl : set multicast TTL. Default value is 2.\n"); + return 1; + } + + if( rootdescurl + || (devlist = upnpDiscover(2000, multicastif, minissdpdpath, + localport, ipv6, ttl, &error))) + { + struct UPNPDev * device; + struct UPNPUrls urls; + struct IGDdatas data; + if(devlist) + { + printf("List of UPNP devices found on the network :\n"); + for(device = devlist; device; device = device->pNext) + { + printf(" desc: %s\n st: %s\n\n", + device->descURL, device->st); + } + } + else if(!rootdescurl) + { + printf("upnpDiscover() error code=%d\n", error); + } + i = 1; + if( (rootdescurl && UPNP_GetIGDFromUrl(rootdescurl, &urls, &data, lanaddr, sizeof(lanaddr))) + || (i = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)))) + { + switch(i) { + case 1: + printf("Found valid IGD : %s\n", urls.controlURL); + break; + case 2: + printf("Found a (not connected?) IGD : %s\n", urls.controlURL); + printf("Trying to continue anyway\n"); + break; + case 3: + printf("UPnP device found. Is it an IGD ? : %s\n", urls.controlURL); + printf("Trying to continue anyway\n"); + break; + default: + printf("Found device (igd ?) : %s\n", urls.controlURL); + printf("Trying to continue anyway\n"); + } + printf("Local LAN ip address : %s\n", lanaddr); + #if 0 + printf("getting \"%s\"\n", urls.ipcondescURL); + descXML = miniwget(urls.ipcondescURL, &descXMLsize); + if(descXML) + { + /*fwrite(descXML, 1, descXMLsize, stdout);*/ + free(descXML); descXML = NULL; + } + #endif + + switch(command) + { + case 'l': + DisplayInfos(&urls, &data); + ListRedirections(&urls, &data); + break; + case 'L': + NewListRedirections(&urls, &data); + break; + case 'a': + if (SetRedirectAndTest(&urls, &data, + commandargv[0], commandargv[1], + commandargv[2], commandargv[3], + (commandargc > 4)?commandargv[4]:"0", + description, 0) < 0) + retcode = 2; + break; + case 'd': + if (RemoveRedirect(&urls, &data, commandargv[0], commandargv[1], + commandargc > 2 ? commandargv[2] : NULL) < 0) + retcode = 2; + break; + case 'n': /* aNy */ + if (SetRedirectAndTest(&urls, &data, + commandargv[0], commandargv[1], + commandargv[2], commandargv[3], + (commandargc > 4)?commandargv[4]:"0", + description, 1) < 0) + retcode = 2; + break; + case 'N': + if (commandargc < 3) + fprintf(stderr, "too few arguments\n"); + + if (RemoveRedirectRange(&urls, &data, commandargv[0], commandargv[1], commandargv[2], + commandargc > 3 ? commandargv[3] : NULL) < 0) + retcode = 2; + break; + case 's': + GetConnectionStatus(&urls, &data); + break; + case 'r': + i = 0; + while(i */ + if (SetRedirectAndTest(&urls, &data, + lanaddr, commandargv[i], + commandargv[i+1], commandargv[i+2], "0", + description, 0) < 0) + retcode = 2; + i+=3; /* 3 parameters parsed */ + } else { + /* 2nd parameter not an integer : */ + if (SetRedirectAndTest(&urls, &data, + lanaddr, commandargv[i], + commandargv[i], commandargv[i+1], "0", + description, 0) < 0) + retcode = 2; + i+=2; /* 2 parameters parsed */ + } + } + break; + case 'A': + SetPinholeAndTest(&urls, &data, + commandargv[0], commandargv[1], + commandargv[2], commandargv[3], + commandargv[4], commandargv[5]); + break; + case 'U': + GetPinholeAndUpdate(&urls, &data, + commandargv[0], commandargv[1]); + break; + case 'C': + for(i=0; i +#include +#include +#include "upnpcommands.h" +#include "miniupnpc.h" +#include "portlistingparse.h" + +static UNSIGNED_INTEGER +my_atoui(const char * s) +{ + return s ? ((UNSIGNED_INTEGER)STRTOUI(s, NULL, 0)) : 0; +} + +/* + * */ +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesSent(const char * controlURL, + const char * servicetype) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + unsigned int r = 0; + char * p; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetTotalBytesSent", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewTotalBytesSent"); + r = my_atoui(p); + ClearNameValueList(&pdata); + return r; +} + +/* + * */ +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesReceived(const char * controlURL, + const char * servicetype) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + unsigned int r = 0; + char * p; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetTotalBytesReceived", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewTotalBytesReceived"); + r = my_atoui(p); + ClearNameValueList(&pdata); + return r; +} + +/* + * */ +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsSent(const char * controlURL, + const char * servicetype) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + unsigned int r = 0; + char * p; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetTotalPacketsSent", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewTotalPacketsSent"); + r = my_atoui(p); + ClearNameValueList(&pdata); + return r; +} + +/* + * */ +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsReceived(const char * controlURL, + const char * servicetype) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + unsigned int r = 0; + char * p; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetTotalPacketsReceived", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewTotalPacketsReceived"); + r = my_atoui(p); + ClearNameValueList(&pdata); + return r; +} + +/* UPNP_GetStatusInfo() call the corresponding UPNP method + * returns the current status and uptime */ +MINIUPNP_LIBSPEC int +UPNP_GetStatusInfo(const char * controlURL, + const char * servicetype, + char * status, + unsigned int * uptime, + char * lastconnerror) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char * p; + char * up; + char * err; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!status && !uptime) + return UPNPCOMMAND_INVALID_ARGS; + + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetStatusInfo", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + up = GetValueFromNameValueList(&pdata, "NewUptime"); + p = GetValueFromNameValueList(&pdata, "NewConnectionStatus"); + err = GetValueFromNameValueList(&pdata, "NewLastConnectionError"); + if(p && up) + ret = UPNPCOMMAND_SUCCESS; + + if(status) { + if(p){ + strncpy(status, p, 64 ); + status[63] = '\0'; + }else + status[0]= '\0'; + } + + if(uptime) { + if(up) + sscanf(up,"%u",uptime); + else + *uptime = 0; + } + + if(lastconnerror) { + if(err) { + strncpy(lastconnerror, err, 64 ); + lastconnerror[63] = '\0'; + } else + lastconnerror[0] = '\0'; + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + return ret; +} + +/* UPNP_GetConnectionTypeInfo() call the corresponding UPNP method + * returns the connection type */ +MINIUPNP_LIBSPEC int +UPNP_GetConnectionTypeInfo(const char * controlURL, + const char * servicetype, + char * connectionType) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!connectionType) + return UPNPCOMMAND_INVALID_ARGS; + + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetConnectionTypeInfo", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewConnectionType"); + /*p = GetValueFromNameValueList(&pdata, "NewPossibleConnectionTypes");*/ + /* PossibleConnectionTypes will have several values.... */ + if(p) { + strncpy(connectionType, p, 64 ); + connectionType[63] = '\0'; + ret = UPNPCOMMAND_SUCCESS; + } else + connectionType[0] = '\0'; + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + return ret; +} + +/* UPNP_GetLinkLayerMaxBitRate() call the corresponding UPNP method. + * Returns 2 values: Downloadlink bandwidth and Uplink bandwidth. + * One of the values can be null + * Note : GetLinkLayerMaxBitRates belongs to WANPPPConnection:1 only + * We can use the GetCommonLinkProperties from WANCommonInterfaceConfig:1 */ +MINIUPNP_LIBSPEC int +UPNP_GetLinkLayerMaxBitRates(const char * controlURL, + const char * servicetype, + unsigned int * bitrateDown, + unsigned int * bitrateUp) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + char * down; + char * up; + char * p; + + if(!bitrateDown && !bitrateUp) + return UPNPCOMMAND_INVALID_ARGS; + + /* shouldn't we use GetCommonLinkProperties ? */ + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetCommonLinkProperties", 0, &bufsize))) { + /*"GetLinkLayerMaxBitRates", 0, &bufsize);*/ + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + /*down = GetValueFromNameValueList(&pdata, "NewDownstreamMaxBitRate");*/ + /*up = GetValueFromNameValueList(&pdata, "NewUpstreamMaxBitRate");*/ + down = GetValueFromNameValueList(&pdata, "NewLayer1DownstreamMaxBitRate"); + up = GetValueFromNameValueList(&pdata, "NewLayer1UpstreamMaxBitRate"); + /*GetValueFromNameValueList(&pdata, "NewWANAccessType");*/ + /*GetValueFromNameValueList(&pdata, "NewPhysicalLinkStatus");*/ + if(down && up) + ret = UPNPCOMMAND_SUCCESS; + + if(bitrateDown) { + if(down) + sscanf(down,"%u",bitrateDown); + else + *bitrateDown = 0; + } + + if(bitrateUp) { + if(up) + sscanf(up,"%u",bitrateUp); + else + *bitrateUp = 0; + } + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + return ret; +} + + +/* UPNP_GetExternalIPAddress() call the corresponding UPNP method. + * if the third arg is not null the value is copied to it. + * at least 16 bytes must be available + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR Either an UPnP error code or an unknown error. + * + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + */ +MINIUPNP_LIBSPEC int +UPNP_GetExternalIPAddress(const char * controlURL, + const char * servicetype, + char * extIpAdd) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!extIpAdd || !controlURL || !servicetype) + return UPNPCOMMAND_INVALID_ARGS; + + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetExternalIPAddress", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + /*printf("external ip = %s\n", GetValueFromNameValueList(&pdata, "NewExternalIPAddress") );*/ + p = GetValueFromNameValueList(&pdata, "NewExternalIPAddress"); + if(p) { + strncpy(extIpAdd, p, 16 ); + extIpAdd[15] = '\0'; + ret = UPNPCOMMAND_SUCCESS; + } else + extIpAdd[0] = '\0'; + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_AddPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration) +{ + struct UPNParg * AddPortMappingArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!inPort || !inClient || !proto || !extPort) + return UPNPCOMMAND_INVALID_ARGS; + + AddPortMappingArgs = calloc(9, sizeof(struct UPNParg)); + if(AddPortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + AddPortMappingArgs[0].elt = "NewRemoteHost"; + AddPortMappingArgs[0].val = remoteHost; + AddPortMappingArgs[1].elt = "NewExternalPort"; + AddPortMappingArgs[1].val = extPort; + AddPortMappingArgs[2].elt = "NewProtocol"; + AddPortMappingArgs[2].val = proto; + AddPortMappingArgs[3].elt = "NewInternalPort"; + AddPortMappingArgs[3].val = inPort; + AddPortMappingArgs[4].elt = "NewInternalClient"; + AddPortMappingArgs[4].val = inClient; + AddPortMappingArgs[5].elt = "NewEnabled"; + AddPortMappingArgs[5].val = "1"; + AddPortMappingArgs[6].elt = "NewPortMappingDescription"; + AddPortMappingArgs[6].val = desc?desc:"libminiupnpc"; + AddPortMappingArgs[7].elt = "NewLeaseDuration"; + AddPortMappingArgs[7].val = leaseDuration?leaseDuration:"0"; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "AddPortMapping", AddPortMappingArgs, + &bufsize))) { + free(AddPortMappingArgs); + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + /*buffer[bufsize] = '\0';*/ + /*puts(buffer);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) { + /*printf("AddPortMapping errorCode = '%s'\n", resVal); */ + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } else { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + free(AddPortMappingArgs); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_AddAnyPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration, + char * reservedPort) +{ + struct UPNParg * AddPortMappingArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!inPort || !inClient || !proto || !extPort) + return UPNPCOMMAND_INVALID_ARGS; + + AddPortMappingArgs = calloc(9, sizeof(struct UPNParg)); + if(AddPortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + AddPortMappingArgs[0].elt = "NewRemoteHost"; + AddPortMappingArgs[0].val = remoteHost; + AddPortMappingArgs[1].elt = "NewExternalPort"; + AddPortMappingArgs[1].val = extPort; + AddPortMappingArgs[2].elt = "NewProtocol"; + AddPortMappingArgs[2].val = proto; + AddPortMappingArgs[3].elt = "NewInternalPort"; + AddPortMappingArgs[3].val = inPort; + AddPortMappingArgs[4].elt = "NewInternalClient"; + AddPortMappingArgs[4].val = inClient; + AddPortMappingArgs[5].elt = "NewEnabled"; + AddPortMappingArgs[5].val = "1"; + AddPortMappingArgs[6].elt = "NewPortMappingDescription"; + AddPortMappingArgs[6].val = desc?desc:"libminiupnpc"; + AddPortMappingArgs[7].elt = "NewLeaseDuration"; + AddPortMappingArgs[7].val = leaseDuration?leaseDuration:"0"; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "AddAnyPortMapping", AddPortMappingArgs, + &bufsize))) { + free(AddPortMappingArgs); + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } else { + char *p; + + p = GetValueFromNameValueList(&pdata, "NewReservedPort"); + if(p) { + strncpy(reservedPort, p, 6); + reservedPort[5] = '\0'; + ret = UPNPCOMMAND_SUCCESS; + } else { + ret = UPNPCOMMAND_INVALID_RESPONSE; + } + } + ClearNameValueList(&pdata); + free(AddPortMappingArgs); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_DeletePortMapping(const char * controlURL, const char * servicetype, + const char * extPort, const char * proto, + const char * remoteHost) +{ + /*struct NameValueParserData pdata;*/ + struct UPNParg * DeletePortMappingArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!extPort || !proto) + return UPNPCOMMAND_INVALID_ARGS; + + DeletePortMappingArgs = calloc(4, sizeof(struct UPNParg)); + if(DeletePortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + DeletePortMappingArgs[0].elt = "NewRemoteHost"; + DeletePortMappingArgs[0].val = remoteHost; + DeletePortMappingArgs[1].elt = "NewExternalPort"; + DeletePortMappingArgs[1].val = extPort; + DeletePortMappingArgs[2].elt = "NewProtocol"; + DeletePortMappingArgs[2].val = proto; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "DeletePortMapping", + DeletePortMappingArgs, &bufsize))) { + free(DeletePortMappingArgs); + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } else { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + free(DeletePortMappingArgs); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_DeletePortMappingRange(const char * controlURL, const char * servicetype, + const char * extPortStart, const char * extPortEnd, + const char * proto, + const char * manage) +{ + struct UPNParg * DeletePortMappingArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!extPortStart || !extPortEnd || !proto || !manage) + return UPNPCOMMAND_INVALID_ARGS; + + DeletePortMappingArgs = calloc(5, sizeof(struct UPNParg)); + if(DeletePortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + DeletePortMappingArgs[0].elt = "NewStartPort"; + DeletePortMappingArgs[0].val = extPortStart; + DeletePortMappingArgs[1].elt = "NewEndPort"; + DeletePortMappingArgs[1].val = extPortEnd; + DeletePortMappingArgs[2].elt = "NewProtocol"; + DeletePortMappingArgs[2].val = proto; + DeletePortMappingArgs[3].elt = "NewManage"; + DeletePortMappingArgs[3].val = manage; + + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "DeletePortMappingRange", + DeletePortMappingArgs, &bufsize))) { + free(DeletePortMappingArgs); + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } else { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + free(DeletePortMappingArgs); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_GetGenericPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * index, + char * extPort, + char * intClient, + char * intPort, + char * protocol, + char * desc, + char * enabled, + char * rHost, + char * duration) +{ + struct NameValueParserData pdata; + struct UPNParg * GetPortMappingArgs; + char * buffer; + int bufsize; + char * p; + int r = UPNPCOMMAND_UNKNOWN_ERROR; + if(!index) + return UPNPCOMMAND_INVALID_ARGS; + intClient[0] = '\0'; + intPort[0] = '\0'; + GetPortMappingArgs = calloc(2, sizeof(struct UPNParg)); + if(GetPortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + GetPortMappingArgs[0].elt = "NewPortMappingIndex"; + GetPortMappingArgs[0].val = index; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetGenericPortMappingEntry", + GetPortMappingArgs, &bufsize))) { + free(GetPortMappingArgs); + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "NewRemoteHost"); + if(p && rHost) + { + strncpy(rHost, p, 64); + rHost[63] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewExternalPort"); + if(p && extPort) + { + strncpy(extPort, p, 6); + extPort[5] = '\0'; + r = UPNPCOMMAND_SUCCESS; + } + p = GetValueFromNameValueList(&pdata, "NewProtocol"); + if(p && protocol) + { + strncpy(protocol, p, 4); + protocol[3] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewInternalClient"); + if(p) + { + strncpy(intClient, p, 16); + intClient[15] = '\0'; + r = 0; + } + p = GetValueFromNameValueList(&pdata, "NewInternalPort"); + if(p) + { + strncpy(intPort, p, 6); + intPort[5] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewEnabled"); + if(p && enabled) + { + strncpy(enabled, p, 4); + enabled[3] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewPortMappingDescription"); + if(p && desc) + { + strncpy(desc, p, 80); + desc[79] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewLeaseDuration"); + if(p && duration) + { + strncpy(duration, p, 16); + duration[15] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + r = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &r); + } + ClearNameValueList(&pdata); + free(GetPortMappingArgs); + return r; +} + +MINIUPNP_LIBSPEC int +UPNP_GetPortMappingNumberOfEntries(const char * controlURL, + const char * servicetype, + unsigned int * numEntries) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char* p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetPortMappingNumberOfEntries", 0, + &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } +#ifdef DEBUG + DisplayNameValueList(buffer, bufsize); +#endif + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "NewPortMappingNumberOfEntries"); + if(numEntries && p) { + *numEntries = 0; + sscanf(p, "%u", numEntries); + ret = UPNPCOMMAND_SUCCESS; + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + return ret; +} + +/* UPNP_GetSpecificPortMappingEntry retrieves an existing port mapping + * the result is returned in the intClient and intPort strings + * please provide 16 and 6 bytes of data */ +MINIUPNP_LIBSPEC int +UPNP_GetSpecificPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * extPort, + const char * proto, + const char * remoteHost, + char * intClient, + char * intPort, + char * desc, + char * enabled, + char * leaseDuration) +{ + struct NameValueParserData pdata; + struct UPNParg * GetPortMappingArgs; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!intPort || !intClient || !extPort || !proto) + return UPNPCOMMAND_INVALID_ARGS; + + GetPortMappingArgs = calloc(4, sizeof(struct UPNParg)); + if(GetPortMappingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + GetPortMappingArgs[0].elt = "NewRemoteHost"; + GetPortMappingArgs[0].val = remoteHost; + GetPortMappingArgs[1].elt = "NewExternalPort"; + GetPortMappingArgs[1].val = extPort; + GetPortMappingArgs[2].elt = "NewProtocol"; + GetPortMappingArgs[2].val = proto; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetSpecificPortMappingEntry", + GetPortMappingArgs, &bufsize))) { + free(GetPortMappingArgs); + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "NewInternalClient"); + if(p) { + strncpy(intClient, p, 16); + intClient[15] = '\0'; + ret = UPNPCOMMAND_SUCCESS; + } else + intClient[0] = '\0'; + + p = GetValueFromNameValueList(&pdata, "NewInternalPort"); + if(p) { + strncpy(intPort, p, 6); + intPort[5] = '\0'; + } else + intPort[0] = '\0'; + + p = GetValueFromNameValueList(&pdata, "NewEnabled"); + if(p && enabled) { + strncpy(enabled, p, 4); + enabled[3] = '\0'; + } + + p = GetValueFromNameValueList(&pdata, "NewPortMappingDescription"); + if(p && desc) { + strncpy(desc, p, 80); + desc[79] = '\0'; + } + + p = GetValueFromNameValueList(&pdata, "NewLeaseDuration"); + if(p && leaseDuration) + { + strncpy(leaseDuration, p, 16); + leaseDuration[15] = '\0'; + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + free(GetPortMappingArgs); + return ret; +} + +/* UPNP_GetListOfPortMappings() + * + * Possible UPNP Error codes : + * 606 Action not Authorized + * 730 PortMappingNotFound - no port mapping is found in the specified range. + * 733 InconsistantParameters - NewStartPort and NewEndPort values are not + * consistent. + */ +MINIUPNP_LIBSPEC int +UPNP_GetListOfPortMappings(const char * controlURL, + const char * servicetype, + const char * startPort, + const char * endPort, + const char * protocol, + const char * numberOfPorts, + struct PortMappingParserData * data) +{ + struct NameValueParserData pdata; + struct UPNParg * GetListOfPortMappingsArgs; + const char * p; + char * buffer; + int bufsize; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!startPort || !endPort || !protocol) + return UPNPCOMMAND_INVALID_ARGS; + + GetListOfPortMappingsArgs = calloc(6, sizeof(struct UPNParg)); + if(GetListOfPortMappingsArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + GetListOfPortMappingsArgs[0].elt = "NewStartPort"; + GetListOfPortMappingsArgs[0].val = startPort; + GetListOfPortMappingsArgs[1].elt = "NewEndPort"; + GetListOfPortMappingsArgs[1].val = endPort; + GetListOfPortMappingsArgs[2].elt = "NewProtocol"; + GetListOfPortMappingsArgs[2].val = protocol; + GetListOfPortMappingsArgs[3].elt = "NewManage"; + GetListOfPortMappingsArgs[3].val = "1"; + GetListOfPortMappingsArgs[4].elt = "NewNumberOfPorts"; + GetListOfPortMappingsArgs[4].val = numberOfPorts?numberOfPorts:"1000"; + + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetListOfPortMappings", + GetListOfPortMappingsArgs, &bufsize))) { + free(GetListOfPortMappingsArgs); + return UPNPCOMMAND_HTTP_ERROR; + } + free(GetListOfPortMappingsArgs); + + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + /*p = GetValueFromNameValueList(&pdata, "NewPortListing");*/ + /*if(p) { + printf("NewPortListing : %s\n", p); + }*/ + /*printf("NewPortListing(%d chars) : %s\n", + pdata.portListingLength, pdata.portListing);*/ + if(pdata.portListing) + { + /*struct PortMapping * pm; + int i = 0;*/ + ParsePortListing(pdata.portListing, pdata.portListingLength, + data); + ret = UPNPCOMMAND_SUCCESS; + /* + for(pm = data->head.lh_first; pm != NULL; pm = pm->entries.le_next) + { + printf("%2d %s %5hu->%s:%-5hu '%s' '%s'\n", + i, pm->protocol, pm->externalPort, pm->internalClient, + pm->internalPort, + pm->description, pm->remoteHost); + i++; + } + */ + /*FreePortListing(&data);*/ + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + + /*printf("%.*s", bufsize, buffer);*/ + + return ret; +} + +/* IGD:2, functions for service WANIPv6FirewallControl:1 */ +MINIUPNP_LIBSPEC int +UPNP_GetFirewallStatus(const char * controlURL, + const char * servicetype, + int * firewallEnabled, + int * inboundPinholeAllowed) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char * fe, *ipa, *p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!firewallEnabled || !inboundPinholeAllowed) + return UPNPCOMMAND_INVALID_ARGS; + + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetFirewallStatus", 0, &bufsize); + if(!buffer) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + fe = GetValueFromNameValueList(&pdata, "FirewallEnabled"); + ipa = GetValueFromNameValueList(&pdata, "InboundPinholeAllowed"); + if(ipa && fe) + ret = UPNPCOMMAND_SUCCESS; + if(fe) + *firewallEnabled = my_atoui(fe); + /*else + *firewallEnabled = 0;*/ + if(ipa) + *inboundPinholeAllowed = my_atoui(ipa); + /*else + *inboundPinholeAllowed = 0;*/ + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + int * opTimeout) +{ + struct UPNParg * GetOutboundPinholeTimeoutArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + char * p; + int ret; + + if(!intPort || !intClient || !proto || !remotePort || !remoteHost) + return UPNPCOMMAND_INVALID_ARGS; + + GetOutboundPinholeTimeoutArgs = calloc(6, sizeof(struct UPNParg)); + if(GetOutboundPinholeTimeoutArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + GetOutboundPinholeTimeoutArgs[0].elt = "RemoteHost"; + GetOutboundPinholeTimeoutArgs[0].val = remoteHost; + GetOutboundPinholeTimeoutArgs[1].elt = "RemotePort"; + GetOutboundPinholeTimeoutArgs[1].val = remotePort; + GetOutboundPinholeTimeoutArgs[2].elt = "Protocol"; + GetOutboundPinholeTimeoutArgs[2].val = proto; + GetOutboundPinholeTimeoutArgs[3].elt = "InternalPort"; + GetOutboundPinholeTimeoutArgs[3].val = intPort; + GetOutboundPinholeTimeoutArgs[4].elt = "InternalClient"; + GetOutboundPinholeTimeoutArgs[4].val = intClient; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetOutboundPinholeTimeout", GetOutboundPinholeTimeoutArgs, &bufsize); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } + else + { + ret = UPNPCOMMAND_SUCCESS; + p = GetValueFromNameValueList(&pdata, "OutboundPinholeTimeout"); + if(p) + *opTimeout = my_atoui(p); + } + ClearNameValueList(&pdata); + free(GetOutboundPinholeTimeoutArgs); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_AddPinhole(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + const char * leaseTime, + char * uniqueID) +{ + struct UPNParg * AddPinholeArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + char * p; + int ret; + + if(!intPort || !intClient || !proto || !remoteHost || !remotePort || !leaseTime) + return UPNPCOMMAND_INVALID_ARGS; + + AddPinholeArgs = calloc(7, sizeof(struct UPNParg)); + if(AddPinholeArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + /* RemoteHost can be wilcarded */ + if(strncmp(remoteHost, "empty", 5)==0) + { + AddPinholeArgs[0].elt = "RemoteHost"; + AddPinholeArgs[0].val = ""; + } + else + { + AddPinholeArgs[0].elt = "RemoteHost"; + AddPinholeArgs[0].val = remoteHost; + } + AddPinholeArgs[1].elt = "RemotePort"; + AddPinholeArgs[1].val = remotePort; + AddPinholeArgs[2].elt = "Protocol"; + AddPinholeArgs[2].val = proto; + AddPinholeArgs[3].elt = "InternalPort"; + AddPinholeArgs[3].val = intPort; + if(strncmp(intClient, "empty", 5)==0) + { + AddPinholeArgs[4].elt = "InternalClient"; + AddPinholeArgs[4].val = ""; + } + else + { + AddPinholeArgs[4].elt = "InternalClient"; + AddPinholeArgs[4].val = intClient; + } + AddPinholeArgs[5].elt = "LeaseTime"; + AddPinholeArgs[5].val = leaseTime; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "AddPinhole", AddPinholeArgs, &bufsize); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "UniqueID"); + if(p) + { + strncpy(uniqueID, p, 8); + uniqueID[7] = '\0'; + } + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) + { + /*printf("AddPortMapping errorCode = '%s'\n", resVal);*/ + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } + else + { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + free(AddPinholeArgs); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_UpdatePinhole(const char * controlURL, const char * servicetype, + const char * uniqueID, + const char * leaseTime) +{ + struct UPNParg * UpdatePinholeArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!uniqueID || !leaseTime) + return UPNPCOMMAND_INVALID_ARGS; + + UpdatePinholeArgs = calloc(3, sizeof(struct UPNParg)); + if(UpdatePinholeArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + UpdatePinholeArgs[0].elt = "UniqueID"; + UpdatePinholeArgs[0].val = uniqueID; + UpdatePinholeArgs[1].elt = "NewLeaseTime"; + UpdatePinholeArgs[1].val = leaseTime; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "UpdatePinhole", UpdatePinholeArgs, &bufsize); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) + { + /*printf("AddPortMapping errorCode = '%s'\n", resVal); */ + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } + else + { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + free(UpdatePinholeArgs); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char * uniqueID) +{ + /*struct NameValueParserData pdata;*/ + struct UPNParg * DeletePinholeArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!uniqueID) + return UPNPCOMMAND_INVALID_ARGS; + + DeletePinholeArgs = calloc(2, sizeof(struct UPNParg)); + if(DeletePinholeArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + DeletePinholeArgs[0].elt = "UniqueID"; + DeletePinholeArgs[0].val = uniqueID; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "DeletePinhole", DeletePinholeArgs, &bufsize); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } + else + { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + free(DeletePinholeArgs); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype, + const char * uniqueID, int * isWorking) +{ + struct NameValueParserData pdata; + struct UPNParg * CheckPinholeWorkingArgs; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!uniqueID) + return UPNPCOMMAND_INVALID_ARGS; + + CheckPinholeWorkingArgs = calloc(4, sizeof(struct UPNParg)); + if(CheckPinholeWorkingArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + CheckPinholeWorkingArgs[0].elt = "UniqueID"; + CheckPinholeWorkingArgs[0].val = uniqueID; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "CheckPinholeWorking", CheckPinholeWorkingArgs, &bufsize); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "IsWorking"); + if(p) + { + *isWorking=my_atoui(p); + ret = UPNPCOMMAND_SUCCESS; + } + else + *isWorking = 0; + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + free(CheckPinholeWorkingArgs); + return ret; +} + +MINIUPNP_LIBSPEC int +UPNP_GetPinholePackets(const char * controlURL, const char * servicetype, + const char * uniqueID, int * packets) +{ + struct NameValueParserData pdata; + struct UPNParg * GetPinholePacketsArgs; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!uniqueID) + return UPNPCOMMAND_INVALID_ARGS; + + GetPinholePacketsArgs = calloc(4, sizeof(struct UPNParg)); + if(GetPinholePacketsArgs == NULL) + return UPNPCOMMAND_MEM_ALLOC_ERROR; + GetPinholePacketsArgs[0].elt = "UniqueID"; + GetPinholePacketsArgs[0].val = uniqueID; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetPinholePackets", GetPinholePacketsArgs, &bufsize); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "PinholePackets"); + if(p) + { + *packets=my_atoui(p); + ret = UPNPCOMMAND_SUCCESS; + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + free(GetPinholePacketsArgs); + return ret; +} + + diff --git a/ext/miniupnpc/upnpcommands.h b/ext/miniupnpc/upnpcommands.h new file mode 100644 index 0000000..22eda5e --- /dev/null +++ b/ext/miniupnpc/upnpcommands.h @@ -0,0 +1,348 @@ +/* $Id: upnpcommands.h,v 1.31 2015/07/21 13:16:55 nanard Exp $ */ +/* Miniupnp project : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2015 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided within this distribution */ +#ifndef UPNPCOMMANDS_H_INCLUDED +#define UPNPCOMMANDS_H_INCLUDED + +#include "upnpreplyparse.h" +#include "portlistingparse.h" +#include "miniupnpc_declspec.h" +#include "miniupnpctypes.h" + +/* MiniUPnPc return codes : */ +#define UPNPCOMMAND_SUCCESS (0) +#define UPNPCOMMAND_UNKNOWN_ERROR (-1) +#define UPNPCOMMAND_INVALID_ARGS (-2) +#define UPNPCOMMAND_HTTP_ERROR (-3) +#define UPNPCOMMAND_INVALID_RESPONSE (-4) +#define UPNPCOMMAND_MEM_ALLOC_ERROR (-5) + +#ifdef __cplusplus +extern "C" { +#endif + +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesSent(const char * controlURL, + const char * servicetype); + +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesReceived(const char * controlURL, + const char * servicetype); + +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsSent(const char * controlURL, + const char * servicetype); + +MINIUPNP_LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsReceived(const char * controlURL, + const char * servicetype); + +/* UPNP_GetStatusInfo() + * status and lastconnerror are 64 byte buffers + * Return values : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error code */ +MINIUPNP_LIBSPEC int +UPNP_GetStatusInfo(const char * controlURL, + const char * servicetype, + char * status, + unsigned int * uptime, + char * lastconnerror); + +/* UPNP_GetConnectionTypeInfo() + * argument connectionType is a 64 character buffer + * Return Values : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error code */ +MINIUPNP_LIBSPEC int +UPNP_GetConnectionTypeInfo(const char * controlURL, + const char * servicetype, + char * connectionType); + +/* UPNP_GetExternalIPAddress() call the corresponding UPNP method. + * if the third arg is not null the value is copied to it. + * at least 16 bytes must be available + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR Either an UPnP error code or an unknown error. + * + * possible UPnP Errors : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. */ +MINIUPNP_LIBSPEC int +UPNP_GetExternalIPAddress(const char * controlURL, + const char * servicetype, + char * extIpAdd); + +/* UPNP_GetLinkLayerMaxBitRates() + * call WANCommonInterfaceConfig:1#GetCommonLinkProperties + * + * return values : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error Code. */ +MINIUPNP_LIBSPEC int +UPNP_GetLinkLayerMaxBitRates(const char* controlURL, + const char* servicetype, + unsigned int * bitrateDown, + unsigned int * bitrateUp); + +/* UPNP_AddPortMapping() + * if desc is NULL, it will be defaulted to "libminiupnpc" + * remoteHost is usually NULL because IGD don't support it. + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR. Either an UPnP error code or an unknown error. + * + * List of possible UPnP errors for AddPortMapping : + * errorCode errorDescription (short) - Description (long) + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization and + * the sender was not authorized. + * 715 WildCardNotPermittedInSrcIP - The source IP address cannot be + * wild-carded + * 716 WildCardNotPermittedInExtPort - The external port cannot be wild-carded + * 718 ConflictInMappingEntry - The port mapping entry specified conflicts + * with a mapping assigned previously to another client + * 724 SamePortValuesRequired - Internal and External port values + * must be the same + * 725 OnlyPermanentLeasesSupported - The NAT implementation only supports + * permanent lease times on port mappings + * 726 RemoteHostOnlySupportsWildcard - RemoteHost must be a wildcard + * and cannot be a specific IP address or DNS name + * 727 ExternalPortOnlySupportsWildcard - ExternalPort must be a wildcard and + * cannot be a specific port value + * 728 NoPortMapsAvailable - There are not enough free ports available to + * complete port mapping. + * 729 ConflictWithOtherMechanisms - Attempted port mapping is not allowed + * due to conflict with other mechanisms. + * 732 WildCardNotPermittedInIntPort - The internal port cannot be wild-carded + */ +MINIUPNP_LIBSPEC int +UPNP_AddPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration); + +/* UPNP_AddAnyPortMapping() + * if desc is NULL, it will be defaulted to "libminiupnpc" + * remoteHost is usually NULL because IGD don't support it. + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR. Either an UPnP error code or an unknown error. + * + * List of possible UPnP errors for AddPortMapping : + * errorCode errorDescription (short) - Description (long) + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization and + * the sender was not authorized. + * 715 WildCardNotPermittedInSrcIP - The source IP address cannot be + * wild-carded + * 716 WildCardNotPermittedInExtPort - The external port cannot be wild-carded + * 728 NoPortMapsAvailable - There are not enough free ports available to + * complete port mapping. + * 729 ConflictWithOtherMechanisms - Attempted port mapping is not allowed + * due to conflict with other mechanisms. + * 732 WildCardNotPermittedInIntPort - The internal port cannot be wild-carded + */ +MINIUPNP_LIBSPEC int +UPNP_AddAnyPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration, + char * reservedPort); + +/* UPNP_DeletePortMapping() + * Use same argument values as what was used for AddPortMapping(). + * remoteHost is usually NULL because IGD don't support it. + * Return Values : + * 0 : SUCCESS + * NON ZERO : error. Either an UPnP error code or an undefined error. + * + * List of possible UPnP errors for DeletePortMapping : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 714 NoSuchEntryInArray - The specified value does not exist in the array */ +MINIUPNP_LIBSPEC int +UPNP_DeletePortMapping(const char * controlURL, const char * servicetype, + const char * extPort, const char * proto, + const char * remoteHost); + +/* UPNP_DeletePortRangeMapping() + * Use same argument values as what was used for AddPortMapping(). + * remoteHost is usually NULL because IGD don't support it. + * Return Values : + * 0 : SUCCESS + * NON ZERO : error. Either an UPnP error code or an undefined error. + * + * List of possible UPnP errors for DeletePortMapping : + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 730 PortMappingNotFound - This error message is returned if no port + * mapping is found in the specified range. + * 733 InconsistentParameters - NewStartPort and NewEndPort values are not consistent. */ +MINIUPNP_LIBSPEC int +UPNP_DeletePortMappingRange(const char * controlURL, const char * servicetype, + const char * extPortStart, const char * extPortEnd, + const char * proto, + const char * manage); + +/* UPNP_GetPortMappingNumberOfEntries() + * not supported by all routers */ +MINIUPNP_LIBSPEC int +UPNP_GetPortMappingNumberOfEntries(const char* controlURL, + const char* servicetype, + unsigned int * num); + +/* UPNP_GetSpecificPortMappingEntry() + * retrieves an existing port mapping + * params : + * in extPort + * in proto + * in remoteHost + * out intClient (16 bytes) + * out intPort (6 bytes) + * out desc (80 bytes) + * out enabled (4 bytes) + * out leaseDuration (16 bytes) + * + * return value : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error Code. + * + * List of possible UPnP errors for _GetSpecificPortMappingEntry : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 714 NoSuchEntryInArray - The specified value does not exist in the array. + */ +MINIUPNP_LIBSPEC int +UPNP_GetSpecificPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * extPort, + const char * proto, + const char * remoteHost, + char * intClient, + char * intPort, + char * desc, + char * enabled, + char * leaseDuration); + +/* UPNP_GetGenericPortMappingEntry() + * params : + * in index + * out extPort (6 bytes) + * out intClient (16 bytes) + * out intPort (6 bytes) + * out protocol (4 bytes) + * out desc (80 bytes) + * out enabled (4 bytes) + * out rHost (64 bytes) + * out duration (16 bytes) + * + * return value : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error Code. + * + * Possible UPNP Error codes : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 606 Action not authorized - The action requested REQUIRES authorization + * and the sender was not authorized. + * 713 SpecifiedArrayIndexInvalid - The specified array index is out of bounds + */ +MINIUPNP_LIBSPEC int +UPNP_GetGenericPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * index, + char * extPort, + char * intClient, + char * intPort, + char * protocol, + char * desc, + char * enabled, + char * rHost, + char * duration); + +/* UPNP_GetListOfPortMappings() Available in IGD v2 + * + * + * Possible UPNP Error codes : + * 606 Action not Authorized + * 730 PortMappingNotFound - no port mapping is found in the specified range. + * 733 InconsistantParameters - NewStartPort and NewEndPort values are not + * consistent. + */ +MINIUPNP_LIBSPEC int +UPNP_GetListOfPortMappings(const char * controlURL, + const char * servicetype, + const char * startPort, + const char * endPort, + const char * protocol, + const char * numberOfPorts, + struct PortMappingParserData * data); + +/* IGD:2, functions for service WANIPv6FirewallControl:1 */ +MINIUPNP_LIBSPEC int +UPNP_GetFirewallStatus(const char * controlURL, + const char * servicetype, + int * firewallEnabled, + int * inboundPinholeAllowed); + +MINIUPNP_LIBSPEC int +UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + int * opTimeout); + +MINIUPNP_LIBSPEC int +UPNP_AddPinhole(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + const char * leaseTime, + char * uniqueID); + +MINIUPNP_LIBSPEC int +UPNP_UpdatePinhole(const char * controlURL, const char * servicetype, + const char * uniqueID, + const char * leaseTime); + +MINIUPNP_LIBSPEC int +UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char * uniqueID); + +MINIUPNP_LIBSPEC int +UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype, + const char * uniqueID, int * isWorking); + +MINIUPNP_LIBSPEC int +UPNP_GetPinholePackets(const char * controlURL, const char * servicetype, + const char * uniqueID, int * packets); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/ext/miniupnpc/upnpdev.c b/ext/miniupnpc/upnpdev.c new file mode 100644 index 0000000..d89a993 --- /dev/null +++ b/ext/miniupnpc/upnpdev.c @@ -0,0 +1,23 @@ +/* $Id: upnpdev.c,v 1.1 2015/08/28 12:14:19 nanard Exp $ */ +/* Project : miniupnp + * Web : http://miniupnp.free.fr/ + * Author : Thomas BERNARD + * copyright (c) 2005-2015 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENSE file. */ +#include +#include "upnpdev.h" + +/* freeUPNPDevlist() should be used to + * free the chained list returned by upnpDiscover() */ +void freeUPNPDevlist(struct UPNPDev * devlist) +{ + struct UPNPDev * next; + while(devlist) + { + next = devlist->pNext; + free(devlist); + devlist = next; + } +} + diff --git a/ext/miniupnpc/upnpdev.h b/ext/miniupnpc/upnpdev.h new file mode 100644 index 0000000..f49fbe1 --- /dev/null +++ b/ext/miniupnpc/upnpdev.h @@ -0,0 +1,36 @@ +/* $Id: upnpdev.h,v 1.1 2015/08/28 12:14:19 nanard Exp $ */ +/* Project : miniupnp + * Web : http://miniupnp.free.fr/ + * Author : Thomas BERNARD + * copyright (c) 2005-2015 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENSE file. */ +#ifndef UPNPDEV_H_INCLUDED +#define UPNPDEV_H_INCLUDED + +#include "miniupnpc_declspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct UPNPDev { + struct UPNPDev * pNext; + char * descURL; + char * st; + unsigned int scope_id; + char * usn; + char buffer[3]; +}; + +/* freeUPNPDevlist() + * free list returned by upnpDiscover() */ +MINIUPNP_LIBSPEC void freeUPNPDevlist(struct UPNPDev * devlist); + + +#ifdef __cplusplus +} +#endif + + +#endif /* UPNPDEV_H_INCLUDED */ diff --git a/ext/miniupnpc/upnperrors.c b/ext/miniupnpc/upnperrors.c new file mode 100644 index 0000000..7ab8ee9 --- /dev/null +++ b/ext/miniupnpc/upnperrors.c @@ -0,0 +1,107 @@ +/* $Id: upnperrors.c,v 1.8 2014/06/10 09:41:48 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas BERNARD + * copyright (c) 2007 Thomas Bernard + * All Right reserved. + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +#include +#include "upnperrors.h" +#include "upnpcommands.h" +#include "miniupnpc.h" + +const char * strupnperror(int err) +{ + const char * s = NULL; + switch(err) { + case UPNPCOMMAND_SUCCESS: + s = "Success"; + break; + case UPNPCOMMAND_UNKNOWN_ERROR: + s = "Miniupnpc Unknown Error"; + break; + case UPNPCOMMAND_INVALID_ARGS: + s = "Miniupnpc Invalid Arguments"; + break; + case UPNPCOMMAND_INVALID_RESPONSE: + s = "Miniupnpc Invalid response"; + break; + case UPNPDISCOVER_SOCKET_ERROR: + s = "Miniupnpc Socket error"; + break; + case UPNPDISCOVER_MEMORY_ERROR: + s = "Miniupnpc Memory allocation error"; + break; + case 401: + s = "Invalid Action"; + break; + case 402: + s = "Invalid Args"; + break; + case 501: + s = "Action Failed"; + break; + case 606: + s = "Action not authorized"; + break; + case 701: + s = "PinholeSpaceExhausted"; + break; + case 702: + s = "FirewallDisabled"; + break; + case 703: + s = "InboundPinholeNotAllowed"; + break; + case 704: + s = "NoSuchEntry"; + break; + case 705: + s = "ProtocolNotSupported"; + break; + case 706: + s = "InternalPortWildcardingNotAllowed"; + break; + case 707: + s = "ProtocolWildcardingNotAllowed"; + break; + case 708: + s = "WildcardNotPermittedInSrcIP"; + break; + case 709: + s = "NoPacketSent"; + break; + case 713: + s = "SpecifiedArrayIndexInvalid"; + break; + case 714: + s = "NoSuchEntryInArray"; + break; + case 715: + s = "WildCardNotPermittedInSrcIP"; + break; + case 716: + s = "WildCardNotPermittedInExtPort"; + break; + case 718: + s = "ConflictInMappingEntry"; + break; + case 724: + s = "SamePortValuesRequired"; + break; + case 725: + s = "OnlyPermanentLeasesSupported"; + break; + case 726: + s = "RemoteHostOnlySupportsWildcard"; + break; + case 727: + s = "ExternalPortOnlySupportsWildcard"; + break; + default: + s = "UnknownError"; + break; + } + return s; +} diff --git a/ext/miniupnpc/upnperrors.h b/ext/miniupnpc/upnperrors.h new file mode 100644 index 0000000..3115aee --- /dev/null +++ b/ext/miniupnpc/upnperrors.h @@ -0,0 +1,26 @@ +/* $Id: upnperrors.h,v 1.6 2015/07/21 13:16:55 nanard Exp $ */ +/* (c) 2007-2015 Thomas Bernard + * All rights reserved. + * MiniUPnP Project. + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +#ifndef UPNPERRORS_H_INCLUDED +#define UPNPERRORS_H_INCLUDED + +#include "miniupnpc_declspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* strupnperror() + * Return a string description of the UPnP error code + * or NULL for undefinded errors */ +MINIUPNP_LIBSPEC const char * strupnperror(int err); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/miniupnpc/upnpreplyparse.c b/ext/miniupnpc/upnpreplyparse.c new file mode 100644 index 0000000..5de5796 --- /dev/null +++ b/ext/miniupnpc/upnpreplyparse.c @@ -0,0 +1,197 @@ +/* $Id: upnpreplyparse.c,v 1.19 2015/07/15 10:29:11 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2015 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include +#include +#include + +#include "upnpreplyparse.h" +#include "minixml.h" + +static void +NameValueParserStartElt(void * d, const char * name, int l) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + data->topelt = 1; + if(l>63) + l = 63; + memcpy(data->curelt, name, l); + data->curelt[l] = '\0'; + data->cdata = NULL; + data->cdatalen = 0; +} + +static void +NameValueParserEndElt(void * d, const char * name, int l) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + struct NameValue * nv; + (void)name; + (void)l; + if(!data->topelt) + return; + if(strcmp(data->curelt, "NewPortListing") != 0) + { + int l; + /* standard case. Limited to n chars strings */ + l = data->cdatalen; + nv = malloc(sizeof(struct NameValue)); + if(nv == NULL) + { + /* malloc error */ +#ifdef DEBUG + fprintf(stderr, "%s: error allocating memory", + "NameValueParserEndElt"); +#endif /* DEBUG */ + return; + } + if(l>=(int)sizeof(nv->value)) + l = sizeof(nv->value) - 1; + strncpy(nv->name, data->curelt, 64); + nv->name[63] = '\0'; + if(data->cdata != NULL) + { + memcpy(nv->value, data->cdata, l); + nv->value[l] = '\0'; + } + else + { + nv->value[0] = '\0'; + } + nv->l_next = data->l_head; /* insert in list */ + data->l_head = nv; + } + data->cdata = NULL; + data->cdatalen = 0; + data->topelt = 0; +} + +static void +NameValueParserGetData(void * d, const char * datas, int l) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + if(strcmp(data->curelt, "NewPortListing") == 0) + { + /* specific case for NewPortListing which is a XML Document */ + data->portListing = malloc(l + 1); + if(!data->portListing) + { + /* malloc error */ +#ifdef DEBUG + fprintf(stderr, "%s: error allocating memory", + "NameValueParserGetData"); +#endif /* DEBUG */ + return; + } + memcpy(data->portListing, datas, l); + data->portListing[l] = '\0'; + data->portListingLength = l; + } + else + { + /* standard case. */ + data->cdata = datas; + data->cdatalen = l; + } +} + +void +ParseNameValue(const char * buffer, int bufsize, + struct NameValueParserData * data) +{ + struct xmlparser parser; + data->l_head = NULL; + data->portListing = NULL; + data->portListingLength = 0; + /* init xmlparser object */ + parser.xmlstart = buffer; + parser.xmlsize = bufsize; + parser.data = data; + parser.starteltfunc = NameValueParserStartElt; + parser.endeltfunc = NameValueParserEndElt; + parser.datafunc = NameValueParserGetData; + parser.attfunc = 0; + parsexml(&parser); +} + +void +ClearNameValueList(struct NameValueParserData * pdata) +{ + struct NameValue * nv; + if(pdata->portListing) + { + free(pdata->portListing); + pdata->portListing = NULL; + pdata->portListingLength = 0; + } + while((nv = pdata->l_head) != NULL) + { + pdata->l_head = nv->l_next; + free(nv); + } +} + +char * +GetValueFromNameValueList(struct NameValueParserData * pdata, + const char * Name) +{ + struct NameValue * nv; + char * p = NULL; + for(nv = pdata->l_head; + (nv != NULL) && (p == NULL); + nv = nv->l_next) + { + if(strcmp(nv->name, Name) == 0) + p = nv->value; + } + return p; +} + +#if 0 +/* useless now that minixml ignores namespaces by itself */ +char * +GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, + const char * Name) +{ + struct NameValue * nv; + char * p = NULL; + char * pname; + for(nv = pdata->head.lh_first; + (nv != NULL) && (p == NULL); + nv = nv->entries.le_next) + { + pname = strrchr(nv->name, ':'); + if(pname) + pname++; + else + pname = nv->name; + if(strcmp(pname, Name)==0) + p = nv->value; + } + return p; +} +#endif + +/* debug all-in-one function + * do parsing then display to stdout */ +#ifdef DEBUG +void +DisplayNameValueList(char * buffer, int bufsize) +{ + struct NameValueParserData pdata; + struct NameValue * nv; + ParseNameValue(buffer, bufsize, &pdata); + for(nv = pdata.l_head; + nv != NULL; + nv = nv->l_next) + { + printf("%s = %s\n", nv->name, nv->value); + } + ClearNameValueList(&pdata); +} +#endif /* DEBUG */ + diff --git a/ext/miniupnpc/upnpreplyparse.h b/ext/miniupnpc/upnpreplyparse.h new file mode 100644 index 0000000..6badd15 --- /dev/null +++ b/ext/miniupnpc/upnpreplyparse.h @@ -0,0 +1,63 @@ +/* $Id: upnpreplyparse.h,v 1.19 2014/10/27 16:33:19 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2013 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef UPNPREPLYPARSE_H_INCLUDED +#define UPNPREPLYPARSE_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +struct NameValue { + struct NameValue * l_next; + char name[64]; + char value[128]; +}; + +struct NameValueParserData { + struct NameValue * l_head; + char curelt[64]; + char * portListing; + int portListingLength; + int topelt; + const char * cdata; + int cdatalen; +}; + +/* ParseNameValue() */ +void +ParseNameValue(const char * buffer, int bufsize, + struct NameValueParserData * data); + +/* ClearNameValueList() */ +void +ClearNameValueList(struct NameValueParserData * pdata); + +/* GetValueFromNameValueList() */ +char * +GetValueFromNameValueList(struct NameValueParserData * pdata, + const char * Name); + +#if 0 +/* GetValueFromNameValueListIgnoreNS() */ +char * +GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, + const char * Name); +#endif + +/* DisplayNameValueList() */ +#ifdef DEBUG +void +DisplayNameValueList(char * buffer, int bufsize); +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/ext/miniupnpc/wingenminiupnpcstrings.c b/ext/miniupnpc/wingenminiupnpcstrings.c new file mode 100644 index 0000000..50df06a --- /dev/null +++ b/ext/miniupnpc/wingenminiupnpcstrings.c @@ -0,0 +1,83 @@ +/* $Id: wingenminiupnpcstrings.c,v 1.4 2015/02/08 08:46:06 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author: Thomas Bernard + * Copyright (c) 2005-2015 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENSE file provided within this distribution */ +#include +#include + +/* This program display the Windows version and is used to + * generate the miniupnpcstrings.h + * wingenminiupnpcstrings miniupnpcstrings.h.in miniupnpcstrings.h + */ +int main(int argc, char * * argv) { + char buffer[256]; + OSVERSIONINFO osvi; + FILE * fin; + FILE * fout; + int n; + char miniupnpcVersion[32]; + /* dwMajorVersion : + The major version number of the operating system. For more information, see Remarks. + dwMinorVersion : + The minor version number of the operating system. For more information, see Remarks. + dwBuildNumber : + The build number of the operating system. + dwPlatformId + The operating system platform. This member can be the following value. + szCSDVersion + A null-terminated string, such as "Service Pack 3", that indicates the + latest Service Pack installed on the system. If no Service Pack has + been installed, the string is empty. + */ + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + GetVersionEx(&osvi); + + printf("Windows %lu.%lu Build %lu %s\n", + osvi.dwMajorVersion, osvi.dwMinorVersion, + osvi.dwBuildNumber, (const char *)&(osvi.szCSDVersion)); + + fin = fopen("VERSION", "r"); + fgets(miniupnpcVersion, sizeof(miniupnpcVersion), fin); + fclose(fin); + for(n = 0; n < sizeof(miniupnpcVersion); n++) { + if(miniupnpcVersion[n] < ' ') + miniupnpcVersion[n] = '\0'; + } + printf("MiniUPnPc version %s\n", miniupnpcVersion); + + if(argc >= 3) { + fin = fopen(argv[1], "r"); + if(!fin) { + fprintf(stderr, "Cannot open %s for reading.\n", argv[1]); + return 1; + } + fout = fopen(argv[2], "w"); + if(!fout) { + fprintf(stderr, "Cannot open %s for writing.\n", argv[2]); + fclose(fin); + return 1; + } + n = 0; + while(fgets(buffer, sizeof(buffer), fin)) { + if(0 == memcmp(buffer, "#define OS_STRING \"OS/version\"", 30)) { + sprintf(buffer, "#define OS_STRING \"MSWindows/%ld.%ld.%ld\"\n", + osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber); + } else if(0 == memcmp(buffer, "#define MINIUPNPC_VERSION_STRING \"version\"", 42)) { + sprintf(buffer, "#define MINIUPNPC_VERSION_STRING \"%s\"\n", + miniupnpcVersion); + } + /*fputs(buffer, stdout);*/ + fputs(buffer, fout); + n++; + } + fclose(fin); + fclose(fout); + printf("%d lines written to %s.\n", n, argv[2]); + } + return 0; +} diff --git a/ext/tap-mac/README.txt b/ext/tap-mac/README.txt new file mode 100644 index 0000000..177b936 --- /dev/null +++ b/ext/tap-mac/README.txt @@ -0,0 +1,19 @@ +This is a hack of tuntaposx. It's here for two reasons: + +1) There seem to be issues with large MTUs in the original tuntap code, + so we set up our zt0 tap with the correct ZeroTier MTU as the default. + +2) Lots of other mac products (VPNs, etc.) ship their own tap device + drivers that like to conflict with one another. This gives us no + choice but to play along. But we call our tap device zt0, which means + it won't conflict with everyone else's tap0. + +3) It's nice to call the device zt0, same as Linux, for consistency across + *nix platforms. Mac does not seem to support interface renaming. + +This will be placed in the ZeroTier home as a kext and is auto-loaded by the +ZeroTier One binary if /dev/zt0 is not found. It can also be auto-updated. + +See this page for the original: + +http://tuntaposx.sourceforge.net diff --git a/ext/tap-mac/tuntap/Makefile b/ext/tap-mac/tuntap/Makefile new file mode 100644 index 0000000..53ab1a9 --- /dev/null +++ b/ext/tap-mac/tuntap/Makefile @@ -0,0 +1,95 @@ +# Lets have a version, at last! +TUNTAP_VERSION = 20150118 + +# BASE install directory +BASE= + +all: tap.kext + +keysetup: + -security delete-keychain net.sf.tuntaposx.tmp + security create-keychain -p $$(head -c 32 /dev/urandom | hexdump -e '"%02x"') \ + net.sf.tuntaposx.tmp + security set-keychain-settings -lut 60 net.sf.tuntaposx.tmp + security import identity.p12 -k net.sf.tuntaposx.tmp -f pkcs12 \ + -P $$(read -sp 'identity passphrase: ' pw && echo "$$pw") -A + security find-identity -v net.sf.tuntaposx.tmp | \ + awk -F \" '$$2 ~ /^Developer ID Application:/ { print $$2 }' > .signing_identity + security find-identity -v net.sf.tuntaposx.tmp | \ + awk -F \" '$$2 ~ /^Developer ID Installer:/ { print $$2 }' > .installer_identity + +pkgbuild/%.pkg: %.kext + mkdir -p pkgbuild/$*_root/Library/Extensions + cp -pR $*.kext pkgbuild/$*_root/Library/Extensions + mkdir -p pkgbuild/$*_root/Library/LaunchDaemons + cp pkg/launchd/net.sf.tuntaposx.$*.plist pkgbuild/$*_root/Library/LaunchDaemons + pkgbuild --root pkgbuild/$*_root \ + --component-plist pkg/components/$*.plist \ + --scripts pkg/scripts/$* pkgbuild/$*.pkg + +tuntap_$(TUNTAP_VERSION).pkg: pkgbuild/tap.pkg pkgbuild/tun.pkg + productbuild --distribution pkg/distribution.xml --package-path pkgbuild \ + --resources pkg/res.dummy \ + tuntap_$(TUNTAP_VERSION).pkg ; \ + pkgutil --expand tuntap_$(TUNTAP_VERSION).pkg pkgbuild/tuntap_pkg.d + cp -pR pkg/res/ pkgbuild/tuntap_pkg.d/Resources + pkgutil --flatten pkgbuild/tuntap_pkg.d tuntap_$(TUNTAP_VERSION).pkg + if test -s ".installer_identity"; then \ + productsign --sign "$$(cat .installer_identity)" --keychain net.sf.tuntaposx.tmp \ + tuntap_$(TUNTAP_VERSION).pkg tuntap_$(TUNTAP_VERSION).pkg.signed ; \ + mv tuntap_$(TUNTAP_VERSION).pkg.signed tuntap_$(TUNTAP_VERSION).pkg ; \ + fi + +pkg: tuntap_$(TUNTAP_VERSION).pkg + tar czf tuntap_$(TUNTAP_VERSION).tar.gz \ + README.installer README tuntap_$(TUNTAP_VERSION).pkg + +# Install targets +# They are provided for the gentoo ebuild, but should work just fine for other people as well. +install_%_kext: %.kext + mkdir -p $(BASE)/Library/Extensions + cp -pR $*.kext $(BASE)/Library/Extensions/ + chown -R root:wheel $(BASE)/Library/Extensions/$*.kext + mkdir -p $(BASE)/Library/LaunchDaemons + cp pkg/launchd/net.sf.tuntaposx.$*.plist $(BASE)/Library/LaunchDaemons + chown -R root:wheel $(BASE)/Library/LaunchDaemons/net.sf.tuntaposx.$*.plist + +install: install_tap_kext install_tun_kext + +tarball: clean + touch tuntap_$(TUNTAP_VERSION)_src.tar.gz + tar czf tuntap_$(TUNTAP_VERSION)_src.tar.gz \ + -C .. \ + --exclude "tuntap/identity.p12" \ + --exclude "tuntap/tuntap_$(TUNTAP_VERSION)_src.tar.gz" \ + --exclude "tuntap/tuntap_$(TUNTAP_VERSION).tar.gz" \ + --exclude "tuntap/tuntap_$(TUNTAP_VERSION).pkg" \ + --exclude "*/.*" \ + tuntap + +clean: + cd src/tap && make -f Makefile clean + cd src/tun && make -f Makefile clean + -rm -rf pkgbuild + -rm -rf tuntap_$(TUNTAP_VERSION).pkg + -rm -f tuntap_$(TUNTAP_VERSION).tar.gz + -rm -f tuntap_$(TUNTAP_VERSION)_src.tar.gz + +%.kext: + cd src/$* && make TUNTAP_VERSION=$(TUNTAP_VERSION) -f Makefile all + if test -s ".signing_identity"; then \ + codesign -fv --keychain net.sf.tuntaposx.tmp -s "$$(cat .signing_identity)" \ + $*.kext ; \ + fi + +test: + # configd messes with interface flags, issuing SIOCSIFFLAGS ioctls upon receiving kernel + # events indicating protocols have been attached and detached. Unfortunately, configd does + # this asynchronously, making the SIOCSIFFLAGS changes totally unpredictable when we bring + # our interfaces up and down in rapid succession during our tests. I haven't found a good + # way to suppress or handle this mess other than disabling configd temporarily. + killall -STOP configd + -PYTHONPATH=test python test/tuntap/tuntap_tests.py --tests='$(TESTS)' + killall -CONT configd + +.PHONY: test diff --git a/ext/tap-mac/tuntap/src/lock.cc b/ext/tap-mac/tuntap/src/lock.cc new file mode 100644 index 0000000..9c78783 --- /dev/null +++ b/ext/tap-mac/tuntap/src/lock.cc @@ -0,0 +1,206 @@ +/* + * ip tunnel/ethertap device for MacOSX. + * + * Locking implementation. + */ +/* + * Copyright (c) 2011 Mattias Nissler + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. The name of the author may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "lock.h" + +extern "C" { + +#include + +#include +#include + +} + +#if 0 +#define dprintf(...) log(LOG_INFO, __VA_ARGS__) +#else +#define dprintf(...) +#endif + +/* class tt_lock */ +lck_grp_t *tt_lock::tt_lck_grp = NULL; + +bool +tt_lock::initialize() +{ + /* init if necessary */ + if (tt_lck_grp == NULL) { + dprintf("initing lock group\n"); + tt_lck_grp = lck_grp_alloc_init("tuntap locks", LCK_GRP_ATTR_NULL); + + if (tt_lck_grp == NULL) { + /* if something fails, the lock won't work */ + log(LOG_ERR, "tuntap: could not allocate locking group\n"); + return false; + } + } + + return true; +} + +void +tt_lock::shutdown() +{ + /* free the locking group */ + if (tt_lck_grp != NULL) { + dprintf("freeing lock group\n"); + lck_grp_free(tt_lck_grp); + tt_lck_grp = NULL; + } +} + +/* tt_mutex */ +tt_mutex::tt_mutex() +{ + /* fail if locking group not initialized */ + if (tt_lck_grp == NULL) + return; + + /* allocate the lock */ + lck = lck_rw_alloc_init(tt_lck_grp, NULL); + + if (lck == NULL) + log(LOG_ERR, "tuntap: could not allocate mutex\n"); +} + +tt_mutex::~tt_mutex() +{ + /* if the lock doesn't exist, this will be a no-op */ + if (lck == NULL) + return; + + /* free the lock */ + lck_rw_free(lck, tt_lck_grp); +} + +void +tt_mutex::lock() +{ + if (lck != NULL) + lck_rw_lock_exclusive(lck); +} + +void +tt_mutex::unlock() +{ + if (lck != NULL) + lck_rw_unlock_exclusive(lck); +} + +void +tt_mutex::sleep(void *cond) +{ + if (lck != NULL) + lck_rw_sleep(lck, LCK_SLEEP_DEFAULT, cond, THREAD_INTERRUPTIBLE); +} + +void +tt_mutex::sleep(void *cond, uint64_t nanoseconds) +{ + if (lck != NULL) { + uint64_t abstime; + nanoseconds_to_absolutetime(nanoseconds, &abstime); + lck_rw_sleep_deadline(lck, LCK_SLEEP_DEFAULT, cond, THREAD_INTERRUPTIBLE, abstime); + } +} + +void +tt_mutex::wakeup(void *cond) +{ + if (lck != NULL) + ::wakeup(cond); +} + +/* tt_gate */ +tt_gate::tt_gate() + : ticket_number(0), + population(0) +{ +} + +void +tt_gate::enter() +{ + /* just try to grab the lock, increase the ticket number and the population */ + auto_lock l(&slock); + ticket_number++; + population++; +} + +void +tt_gate::exit() +{ + auto_lock l(&slock); + ticket_number--; + population--; +} + +bool +tt_gate::is_anyone_in() +{ + return population != 0; +} + +unsigned int +tt_gate::get_ticket_number() +{ + return ticket_number; +} + +void +tt_gate::lock() +{ + slock.lock(); +} + +void +tt_gate::unlock() +{ + slock.unlock(); +} + +void +tt_gate::sleep(void* cond) +{ + slock.sleep(cond); +} + +void +tt_gate::sleep(void* cond, uint64_t nanoseconds) +{ + slock.sleep(cond, nanoseconds); +} + +void +tt_gate::wakeup(void* cond) +{ + slock.wakeup(cond); +} + diff --git a/ext/tap-mac/tuntap/src/lock.h b/ext/tap-mac/tuntap/src/lock.h new file mode 100644 index 0000000..51d3299 --- /dev/null +++ b/ext/tap-mac/tuntap/src/lock.h @@ -0,0 +1,160 @@ +/* + * ip tunnel/ethertap device for MacOSX. + * + * Locking is not as straightforward for Tiger. So declare our own locking class. + */ +/* + * Copyright (c) 2011 Mattias Nissler + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. The name of the author may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __LOCK_H__ +#define __LOCK_H__ + +extern "C" { + +#include +#include + +} + +/* our own locking class. declares the common interface of the locking primitives. */ +class tt_lock { + + protected: + /* locking group */ + static lck_grp_t *tt_lck_grp; + + public: + /* be virtual */ + virtual ~tt_lock() { }; + + /* static intialization (inits the locking group) */ + static bool initialize(); + static void shutdown(); + + /* locking */ + virtual void lock() = 0; + virtual void unlock() = 0; + + /* monitor primitives */ + virtual void sleep(void* cond) = 0; + virtual void sleep(void* cond, uint64_t) = 0; + virtual void wakeup(void* cond) = 0; +}; + +/* simple mutex */ +class tt_mutex : public tt_lock { + + private: + /* underlying darwin lock */ + lck_rw_t *lck; + + public: + tt_mutex(); + virtual ~tt_mutex(); + + void lock(); + void unlock(); + + /* monitor primitives */ + void sleep(void* cond); + void sleep(void* cond, uint64_t); + void wakeup(void* cond); +}; + +/* A very special locking class that we use to track threads that enter and leave the character + * device service functions. They call enter() before entering the actual service routinge and + * exit() when done. enter() only permits them to pass when the gate isn't locked. Furthermore, the + * gate assigns ticket numbers to everyone that passes the gate, so you can check whether more + * threads came through. See tuntap_mgr::shutdown() for how we use that stuff. + */ +class tt_gate : public tt_lock { + + private: + /* synchronization lock */ + tt_mutex slock; + /* ticket number */ + unsigned int ticket_number; + /* count of threads that are in */ + unsigned int population; + + public: + /* construct a new gate */ + tt_gate(); + + /* enter - pass the gate */ + void enter(); + /* exit - pass the gate */ + void exit(); + + /* check whether anyone is in */ + bool is_anyone_in(); + /* gets the next ticket number */ + unsigned int get_ticket_number(); + + /* lock the gate */ + void lock(); + /* unlock the gate */ + void unlock(); + + /* monitor primitives */ + void sleep(void* cond); + void sleep(void* cond, uint64_t); + void wakeup(void* cond); +}; + +/* auto_lock and auto_rwlock serve as automatic lock managers: Create an object, passing the + * tt_[rw]lock you want to lock to have it grab the lock. When the object goes out of scope, the + * destructor of the class will release the lock. + */ +class auto_lock { + + protected: + /* the lock we hold */ + tt_lock *l; + + public: + auto_lock(tt_lock *m) + : l(m) + { + lock(); + } + + ~auto_lock() + { + unlock(); + } + + void lock() + { + l->lock(); + } + + void unlock() + { + l->unlock(); + } +}; + +#endif /* __LOCK_H__ */ + diff --git a/ext/tap-mac/tuntap/src/mem.cc b/ext/tap-mac/tuntap/src/mem.cc new file mode 100644 index 0000000..cd3264f --- /dev/null +++ b/ext/tap-mac/tuntap/src/mem.cc @@ -0,0 +1,76 @@ +/* + * ip tunnel/ethertap device for MacOSX. Common functionality of tap_interface and tun_interface. + * + * Memory management implementation. + */ +/* + * Copyright (c) 2011 Mattias Nissler + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. The name of the author may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "mem.h" + +extern "C" { + +#include + +} + +#if 0 +#define dprintf(...) log(LOG_INFO, __VA_ARGS__) +#else +#define dprintf(...) +#endif + +static int inited = 0; +static OSMallocTag tag; + +void +mem_initialize(const char* name) { + + if (!inited) { + tag = OSMalloc_Tagalloc(name, OSMT_DEFAULT); + inited = 1; + } +} + +void +mem_shutdown() { + + if (inited) { + OSMalloc_Tagfree(tag); + inited = 0; + } +} + +void * +mem_alloc(uint32_t size) { + + return OSMalloc(size, tag); +} + +void +mem_free(void *addr, uint32_t size) { + + OSFree(addr, size, tag); +} + diff --git a/ext/tap-mac/tuntap/src/mem.h b/ext/tap-mac/tuntap/src/mem.h new file mode 100644 index 0000000..4d06fd8 --- /dev/null +++ b/ext/tap-mac/tuntap/src/mem.h @@ -0,0 +1,48 @@ +/* + * ip tunnel/ethertap device for MacOSX. Common functionality of tap_interface and tun_interface. + * + * Memory management. + */ +/* + * Copyright (c) 2011 Mattias Nissler + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. The name of the author may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MEM_H__ +#define __MEM_H__ + +extern "C" { + +#include + +} + +/* Memory manager initalization and shutdown */ +void mem_initialize(const char *name); +void mem_shutdown(); + +/* Memory allocation functions */ +void *mem_alloc(uint32_t size); +void mem_free(void *addr, uint32_t size); + +#endif /* __MEM_H__ */ + diff --git a/ext/tap-mac/tuntap/src/tap/Info.plist b/ext/tap-mac/tuntap/src/tap/Info.plist new file mode 100644 index 0000000..bb9b03f --- /dev/null +++ b/ext/tap-mac/tuntap/src/tap/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + @@CFBUNDLEDEVELOPMENTREGION@@ + CFBundleExecutable + @@CFBUNDLEEXECUTABLE@@ + CFBundleIdentifier + @@CFBUNDLEIDENTIFIER@@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + @@CFBUNDLEEXECUTABLE@@ + CFBundlePackageType + @@CFBUNDLEPACKAGETYPE@@ + CFBundleShortVersionString + @@CFBUNDLEVERSION@@ + CFBundleSignature + @@CFBUNDLESIGNATURE@@ + CFBundleVersion + 1.0 + OSBundleLibraries + + com.apple.kpi.mach + 8.0 + com.apple.kpi.bsd + 8.0 + com.apple.kpi.libkern + 8.0 + com.apple.kpi.unsupported + 8.0 + + + + diff --git a/ext/tap-mac/tuntap/src/tap/Makefile b/ext/tap-mac/tuntap/src/tap/Makefile new file mode 100644 index 0000000..306a86d --- /dev/null +++ b/ext/tap-mac/tuntap/src/tap/Makefile @@ -0,0 +1,60 @@ +# +# ethertap driver for MacOSX +# +# Makefile +# +# (c) 2004, 2005, 2006, 2007, 2008 Mattias Nissler +# + +OBJS = ../tuntap.o ../tuntap_mgr.o ../lock.o ../mem.o kmod.o tap.o +KMOD_BIN = tap +BUNDLE_DIR = ../.. +BUNDLE_NAME = tap.kext + +TAP_KEXT_VERSION = $(TUNTAP_VERSION) + +BUNDLE_REGION = English +BUNDLE_IDENTIFIER = com.zerotier.tap +BUNDLE_SIGNATURE = ???? +BUNDLE_PACKAGETYPE = KEXT +BUNDLE_VERSION = $(TAP_KEXT_VERSION) + +INCLUDE = -I.. -I/System/Library/Frameworks/Kernel.framework/Headers +CFLAGS = -Wall -Werror -mkernel -force_cpusubtype_ALL \ + -nostdinc -fno-builtin -fno-stack-protector -msoft-float -fno-common \ + -arch x86_64 \ + -DKERNEL -DAPPLE -DKERNEL_PRIVATE -DTUNTAP_VERSION=\"$(TUNTAP_VERSION)\" \ + -DTAP_KEXT_VERSION=\"$(TAP_KEXT_VERSION)\" +CCFLAGS = $(CFLAGS) +LDFLAGS = -Wall -Werror -arch x86_64 -Xlinker -kext -nostdlib -lkmodc++ -lkmod -lcc_kext + +CCP = clang -x c++ +CC = clang -x c +LD = clang + +all: $(KMOD_BIN) bundle + +.c.o: + $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@ +.cc.o: + $(CCP) $(CCFLAGS) $(INCLUDE) -c $< -o $@ + +$(KMOD_BIN): $(OBJS) + $(LD) $(LDFLAGS) -o $(KMOD_BIN) $(OBJS) + +bundle: $(KMOD_BIN) + rm -rf $(BUNDLE_DIR)/$(BUNDLE_NAME) + mkdir -p $(BUNDLE_DIR)/$(BUNDLE_NAME)/Contents/MacOS + cp $(KMOD_BIN) $(BUNDLE_DIR)/$(BUNDLE_NAME)/Contents/MacOS + sed -e "s/@@CFBUNDLEEXECUTABLE@@/$(KMOD_BIN)/" \ + -e "s/@@CFBUNDLEDEVELOPMENTREGION@@/$(BUNDLE_REGION)/" \ + -e "s/@@CFBUNDLEIDENTIFIER@@/$(BUNDLE_IDENTIFIER)/" \ + -e "s/@@CFBUNDLESIGNATURE@@/$(BUNDLE_SIGNATURE)/" \ + -e "s/@@CFBUNDLEPACKAGETYPE@@/$(BUNDLE_PACKAGETYPE)/" \ + -e "s/@@CFBUNDLEVERSION@@/$(BUNDLE_VERSION)/" \ + Info.plist > $(BUNDLE_DIR)/$(BUNDLE_NAME)/Contents/Info.plist + +clean: + -rm -f $(OBJS) $(KMOD_BIN) + -rm -rf $(BUNDLE_DIR)/$(BUNDLE_NAME) + diff --git a/ext/tap-mac/tuntap/src/tap/kmod.cc b/ext/tap-mac/tuntap/src/tap/kmod.cc new file mode 100644 index 0000000..f9c4a40 --- /dev/null +++ b/ext/tap-mac/tuntap/src/tap/kmod.cc @@ -0,0 +1,93 @@ +/* + * ethertap device for MacOSX. + * + * Kext definition (it is a mach kmod really...) + */ +/* + * Copyright (c) 2011 Mattias Nissler + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. The name of the author may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tap.h" +#include "mem.h" + +extern "C" { + +#include + +#include + +static tap_manager *mgr; + +/* + * start function. called when the kext gets loaded. + */ +static kern_return_t tap_module_start(struct kmod_info *ki, void *data) +{ + mem_initialize(TAP_FAMILY_NAME); + + /* initialize locking */ + if (!tt_lock::initialize()) + return KMOD_RETURN_FAILURE; + + /* create a tap manager that will handle the rest */ + mgr = new tap_manager(); + + if (mgr != NULL) { + if (mgr->initialize(TAP_IF_COUNT, (char *) TAP_FAMILY_NAME)) + return KMOD_RETURN_SUCCESS; + + delete mgr; + mgr = NULL; + /* clean up locking */ + tt_lock::shutdown(); + } + + return KMOD_RETURN_FAILURE; +} + +/* + * stop function. called when the kext should be unloaded. unloading can be prevented by + * returning failure + */ +static kern_return_t tap_module_stop(struct kmod_info *ki, void *data) +{ + if (mgr != NULL) { + if (!mgr->shutdown()) + return KMOD_RETURN_FAILURE; + + delete mgr; + mgr = NULL; + } + + /* clean up locking */ + tt_lock::shutdown(); + + mem_shutdown(); + + return KMOD_RETURN_SUCCESS; +} + +KMOD_DECL(tap, TAP_KEXT_VERSION) + +} + diff --git a/ext/tap-mac/tuntap/src/tap/tap.cc b/ext/tap-mac/tuntap/src/tap/tap.cc new file mode 100644 index 0000000..b348a85 --- /dev/null +++ b/ext/tap-mac/tuntap/src/tap/tap.cc @@ -0,0 +1,533 @@ +/* + * ethertap device for macosx. + * + * tap_interface class definition + */ +/* + * Copyright (c) 2011 Mattias Nissler + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. The name of the author may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tap.h" + +extern "C" { + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +} + +#if 0 +#define dprintf(...) log(LOG_INFO, __VA_ARGS__) +#else +#define dprintf(...) +#endif + +// These declarations are missing in the Kernel.framework headers, put present in userspace :-/ +#pragma pack(4) +struct ifmediareq { + char ifm_name[IFNAMSIZ]; /* if name, e.g. "en0" */ + int ifm_current; /* current media options */ + int ifm_mask; /* don't care mask */ + int ifm_status; /* media status */ + int ifm_active; /* active options */ + int ifm_count; /* # entries in ifm_ulist array */ + int *ifm_ulist; /* media words */ +}; + +struct ifmediareq64 { + char ifm_name[IFNAMSIZ]; /* if name, e.g. "en0" */ + int ifm_current; /* current media options */ + int ifm_mask; /* don't care mask */ + int ifm_status; /* media status */ + int ifm_active; /* active options */ + int ifm_count; /* # entries in ifm_ulist array */ + user64_addr_t ifmu_ulist __attribute__((aligned(8))); +}; + +struct ifmediareq32 { + char ifm_name[IFNAMSIZ]; /* if name, e.g. "en0" */ + int ifm_current; /* current media options */ + int ifm_mask; /* don't care mask */ + int ifm_status; /* media status */ + int ifm_active; /* active options */ + int ifm_count; /* # entries in ifm_ulist array */ + user32_addr_t ifmu_ulist; /* 32-bit pointer */ +}; +#pragma pack() + +#define SIOCGIFMEDIA32 _IOWR('i', 56, struct ifmediareq32) /* get net media */ +#define SIOCGIFMEDIA64 _IOWR('i', 56, struct ifmediareq64) /* get net media (64-bit) */ + +/* thread_policy_set is exported in Mach.kext, but commented in mach/thread_policy.h in the + * Kernel.Framework headers (why?). Add a local declaration to work around that. + */ +extern "C" { +kern_return_t thread_policy_set( + thread_t thread, + thread_policy_flavor_t flavor, + thread_policy_t policy_info, + mach_msg_type_number_t count); +} + +static unsigned char ETHER_BROADCAST_ADDR[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +/* members */ +tap_interface::tap_interface() { + bzero(attached_protos, sizeof(attached_protos)); + input_thread = THREAD_NULL; +} + +bool +tap_interface::initialize(unsigned short major, unsigned short unit) +{ + this->unit = unit; + this->family_name = TAP_FAMILY_NAME; + this->family = IFNET_FAMILY_ETHERNET; + this->type = IFT_ETHER; + bzero(unique_id, UIDLEN); + snprintf(unique_id, UIDLEN, "%s%d", family_name, unit); + + dprintf("tap: starting interface %s%d\n", TAP_FAMILY_NAME, unit); + + /* register character device */ + if (!tuntap_interface::register_chardev(major)) + return false; + + return true; +} + +void +tap_interface::shutdown() +{ + dprintf("tap: shutting down tap interface %s%d\n", TAP_FAMILY_NAME, unit); + + unregister_chardev(); +} + +int +tap_interface::initialize_interface() +{ + struct sockaddr_dl lladdr; + lladdr.sdl_len = sizeof(lladdr); + lladdr.sdl_family = AF_LINK; + lladdr.sdl_alen = ETHER_ADDR_LEN; + lladdr.sdl_nlen = lladdr.sdl_slen = 0; + + /* generate a random MAC address */ + read_random(LLADDR(&lladdr), ETHER_ADDR_LEN); + + /* clear multicast bit and set local assignment bit (see IEEE 802) */ + (LLADDR(&lladdr))[0] &= 0xfe; + (LLADDR(&lladdr))[0] |= 0x02; + + dprintf("tap: random tap address: %02x:%02x:%02x:%02x:%02x:%02x\n", + (LLADDR(&lladdr))[0] & 0xff, + (LLADDR(&lladdr))[1] & 0xff, + (LLADDR(&lladdr))[2] & 0xff, + (LLADDR(&lladdr))[3] & 0xff, + (LLADDR(&lladdr))[4] & 0xff, + (LLADDR(&lladdr))[5] & 0xff); + + /* register interface */ + if (!tuntap_interface::register_interface(&lladdr, ETHER_BROADCAST_ADDR, ETHER_ADDR_LEN)) + return EIO; + + /* Set link level address. Yes, we need to do that again. Darwin sucks. */ + errno_t err = ifnet_set_lladdr(ifp, LLADDR(&lladdr), ETHER_ADDR_LEN); + if (err) + dprintf("tap: failed to set lladdr on %s%d: %d\n", family_name, unit, err); + + /* set mtu */ + ifnet_set_mtu(ifp, TAP_MTU); + /* set header length */ + ifnet_set_hdrlen(ifp, sizeof(struct ether_header)); + /* add the broadcast flag */ + ifnet_set_flags(ifp, IFF_BROADCAST, IFF_BROADCAST); + + /* we must call bpfattach(). Otherwise we deadlock BPF while unloading. Seems to be a bug in + * the kernel, see bpfdetach() in net/bpf.c, it will return without releasing the lock if + * the interface wasn't attached. I wonder what they were smoking while writing it ;-) + */ + bpfattach(ifp, DLT_EN10MB, ifnet_hdrlen(ifp)); + + /* Inject an empty packet to trigger the input thread calling demux(), which will unblock + * thread_sync_lock. This is part of a hack to avoid a kernel crash on re-attaching + * interfaces, see comment in shutdown_interface for more information. + */ + mbuf_t empty_mbuf; + mbuf_gethdr(MBUF_WAITOK, MBUF_TYPE_DATA, &empty_mbuf); + if (empty_mbuf != NULL) { + mbuf_pkthdr_setrcvif(empty_mbuf, ifp); + mbuf_pkthdr_setlen(empty_mbuf, 0); + mbuf_pkthdr_setheader(empty_mbuf, mbuf_data(empty_mbuf)); + mbuf_set_csum_performed(empty_mbuf, 0, 0); + if (ifnet_input(ifp, empty_mbuf, NULL) == 0) { + auto_lock l(&thread_sync_lock); + for (int i = 0; i < 100 && input_thread == THREAD_NULL; ++i) { + dprintf("input thread not found, waiting...\n"); + thread_sync_lock.sleep(&input_thread, 10000000); + } + } else { + mbuf_freem(empty_mbuf); + } + } + if (input_thread == THREAD_NULL) + dprintf("Failed to determine input thread!\n"); + + return 0; +} + +void +tap_interface::shutdown_interface() +{ + dprintf("tap: shutting down network interface of device %s%d\n", TAP_FAMILY_NAME, unit); + + /* detach all protocols */ + for (unsigned int i = 0; i < MAX_ATTACHED_PROTOS; i++) { + if (attached_protos[i].used) { + errno_t err = ifnet_detach_protocol(ifp, attached_protos[i].proto); + if (err) + log(LOG_WARNING, "tap: could not detach protocol %d from %s%d\n", + attached_protos[i].proto, TAP_FAMILY_NAME, unit); + } + } + + cleanup_interface(); + unregister_interface(); + + /* There's a race condition in the kernel that may cause crashes when quickly re-attaching + * interfaces. The crash happens when the interface gets re-attached before the input thread + * for the interface managed to terminate, in which case an assert on the input_waiting flag + * to be clear triggers in ifnet_attach. The bug is really that there's no synchronization + * for terminating the input thread. To work around this, the following code does add the + * missing synchronization to wait for the input thread to terminate. Of course, threading + * primitives available to kexts are few, and I'm not aware of a way to wait for a thread to + * terminate. Hence, the code calls thread_policy_set (passing bogus parameters) in a loop, + * until it returns KERN_TERMINATED. Since this is all rather fragile, there's an upper + * limit on the loop iteratations we're willing to make, so this terminates eventually even + * if things change on the kernel side eventually. + */ + if (input_thread != THREAD_NULL) { + dprintf("Waiting for input thread...\n"); + kern_return_t result = 0; + for (int i = 0; i < 100; ++i) { + result = thread_policy_set(input_thread, -1, NULL, 0); + dprintf("thread_policy_set result: %d\n", result); + if (result == KERN_TERMINATED) { + dprintf("Input thread terminated.\n"); + thread_deallocate(input_thread); + input_thread = THREAD_NULL; + break; + } + + auto_lock l(&thread_sync_lock); + thread_sync_lock.sleep(&input_thread, 10000000); + } + } +} + +errno_t +tap_interface::if_ioctl(u_int32_t cmd, void *arg) +{ + dprintf("tap: if_ioctl cmd: %d (%x)\n", cmd & 0xff, cmd); + + switch (cmd) { + case SIOCSIFLLADDR: + { + /* set ethernet address */ + struct sockaddr *ea = &(((struct ifreq *) arg)->ifr_addr); + + dprintf("tap: SIOCSIFLLADDR family %d len %d\n", + ea->sa_family, ea->sa_len); + + /* check if it is really an ethernet address */ + if (ea->sa_family != AF_LINK || ea->sa_len != ETHER_ADDR_LEN) + return EINVAL; + + /* ok, copy */ + errno_t err = ifnet_set_lladdr(ifp, ea->sa_data, ETHER_ADDR_LEN); + if (err) { + dprintf("tap: failed to set lladdr on %s%d: %d\n", + family_name, unit, err); + return err; + } + + /* Generate a LINK_ON event. This necessary for configd to re-read + * the interface data and refresh the MAC address. Not doing so + * would result in the DHCP client using a stale MAC address... + */ + generate_link_event(KEV_DL_LINK_ON); + + return 0; + } + + case SIOCGIFMEDIA32: + case SIOCGIFMEDIA64: + { + struct ifmediareq *ifmr = (struct ifmediareq*) arg; + user_addr_t list = USER_ADDR_NULL; + + ifmr->ifm_current = IFM_ETHER; + ifmr->ifm_mask = 0; + ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE; + ifmr->ifm_active = IFM_ETHER; + ifmr->ifm_count = 1; + + if (cmd == SIOCGIFMEDIA64) + list = ((struct ifmediareq64*) ifmr)->ifmu_ulist; + else + list = CAST_USER_ADDR_T( + ((struct ifmediareq32*) ifmr)->ifmu_ulist); + + if (list != USER_ADDR_NULL) + return copyout(&ifmr->ifm_current, list, sizeof(int)); + + return 0; + } + + default: + /* let our superclass handle it */ + return tuntap_interface::if_ioctl(cmd, arg); + } + + return EOPNOTSUPP; +} + +errno_t +tap_interface::if_demux(mbuf_t m, char *header, protocol_family_t *proto) +{ + struct ether_header *eh = (struct ether_header *) header; + unsigned char lladdr[ETHER_ADDR_LEN]; + + dprintf("tap: if_demux\n"); + + /* Make note of what input thread this interface is running on. This is part of a hack to + * avoid a crash on re-attaching interfaces, see comment in shutdown_interface for details. + */ + if (input_thread == THREAD_NULL) { + auto_lock l(&thread_sync_lock); + input_thread = current_thread(); + thread_reference(input_thread); + thread_sync_lock.wakeup(&input_thread); + } + + /* size check */ + if (mbuf_len(m) < sizeof(struct ether_header)) + return ENOENT; + + /* catch broadcast and multicast (stolen from bsd/net/ether_if_module.c) */ + if (eh->ether_dhost[0] & 1) { + if (memcmp(ETHER_BROADCAST_ADDR, eh->ether_dhost, ETHER_ADDR_LEN) == 0) { + /* broadcast */ + dprintf("tap: broadcast packet.\n"); + mbuf_setflags_mask(m, MBUF_BCAST, MBUF_BCAST); + } else { + /* multicast */ + dprintf("tap: multicast packet.\n"); + mbuf_setflags_mask(m, MBUF_MCAST, MBUF_MCAST); + } + } else { + /* check wether the packet has our address */ + ifnet_lladdr_copy_bytes(ifp, lladdr, ETHER_ADDR_LEN); + if (memcmp(lladdr, eh->ether_dhost, ETHER_ADDR_LEN) != 0) + mbuf_setflags_mask(m, MBUF_PROMISC, MBUF_PROMISC); + } + + /* find the protocol */ + for (unsigned int i = 0; i < MAX_ATTACHED_PROTOS; i++) { + if (attached_protos[i].used && attached_protos[i].type == eh->ether_type) { + *proto = attached_protos[i].proto; + return 0; + } + } + + dprintf("tap: if_demux() failed to find proto.\n"); + + /* no matching proto found */ + return ENOENT; +} + +errno_t +tap_interface::if_framer(mbuf_t *m, const struct sockaddr *dest, const char *dest_linkaddr, + const char *frame_type) +{ + struct ether_header *eh; + mbuf_t nm = *m; + errno_t err; + + dprintf("tap: if_framer\n"); + + /* prepend the ethernet header */ + err = mbuf_prepend(&nm, sizeof (struct ether_header), MBUF_WAITOK); + if (err) { + dprintf("tap: could not prepend data to mbuf: %d\n", err); + return err; + } + *m = nm; + + /* fill the header */ + eh = (struct ether_header *) mbuf_data(*m); + memcpy(eh->ether_dhost, dest_linkaddr, ETHER_ADDR_LEN); + ifnet_lladdr_copy_bytes(ifp, eh->ether_shost, ETHER_ADDR_LEN); + eh->ether_type = *((u_int16_t *) frame_type); + + return 0; +} + +errno_t +tap_interface::if_add_proto(protocol_family_t proto, const struct ifnet_demux_desc *desc, + u_int32_t ndesc) +{ + errno_t err; + + dprintf("tap: if_add_proto proto %d\n", proto); + + for (unsigned int i = 0; i < ndesc; i++) { + /* try to add the protocol */ + err = add_one_proto(proto, desc[i]); + if (err != 0) { + /* if that fails, remove everything stored so far */ + if_del_proto(proto); + return err; + } + } + + return 0; +} + +errno_t +tap_interface::if_del_proto(protocol_family_t proto) +{ + dprintf("tap: if_del_proto proto %d\n", proto); + + /* delete all matching entries in attached_protos */ + for (unsigned int i = 0; i < MAX_ATTACHED_PROTOS; i++) { + if (attached_protos[i].proto == proto) + attached_protos[i].used = false; + } + + return 0; +} + +errno_t +tap_interface::if_check_multi(const struct sockaddr *maddr) +{ + dprintf("tap: if_check_multi family %d\n", maddr->sa_family); + + /* see whether it is a ethernet address with the multicast bit set */ + if (maddr->sa_family == AF_LINK) { + struct sockaddr_dl *dlmaddr = (struct sockaddr_dl *) maddr; + if (LLADDR(dlmaddr)[0] & 0x01) + return 0; + else + return EADDRNOTAVAIL; + } + + return EOPNOTSUPP; +} + +errno_t +tap_interface::add_one_proto(protocol_family_t proto, const struct ifnet_demux_desc &dd) +{ + int free = -1; + u_int16_t dt; + + /* we only support DLIL_DESC_ETYPE2 */ + if (dd.type != DLIL_DESC_ETYPE2 || dd.datalen != 2) { + log(LOG_WARNING, "tap: tap only supports DLIL_DESC_ETYPE2 protocols.\n"); + return EINVAL; + } + + dt = *((u_int16_t *) (dd.data)); + + /* see if the protocol is already registered */ + for (unsigned int i = 0; i < MAX_ATTACHED_PROTOS; i++) { + if (attached_protos[i].used) { + if (dt == attached_protos[i].type) { + /* already registered */ + if (attached_protos[i].proto == proto) { + /* matches the old entry */ + return 0; + } else + return EEXIST; + } + } else if (free == -1) + free = i; + } + + /* did we find a free entry? */ + if (free == -1) + /* is ENOBUFS correct? */ + return ENOBUFS; + + /* ok, save information */ + attached_protos[free].used = true; + attached_protos[free].type = dt; + attached_protos[free].proto = proto; + + return 0; +} + +/* This code is shamelessly stolen from if_bond.c */ +void +tap_interface::generate_link_event(u_int32_t code) +{ + struct { + struct kern_event_msg header; + u_int32_t unit; + char if_name[IFNAMSIZ]; + } event; + + bzero(&event, sizeof(event)); + event.header.total_size = sizeof(event); + event.header.vendor_code = KEV_VENDOR_APPLE; + event.header.kev_class = KEV_NETWORK_CLASS; + event.header.kev_subclass = KEV_DL_SUBCLASS; + event.header.event_code = code; + event.header.event_data[0] = family; + event.unit = (u_int32_t) unit; + strncpy(event.if_name, ifnet_name(ifp), IFNAMSIZ); + + ifnet_event(ifp, &event.header); +} + +/* tap_manager members */ +tuntap_interface * +tap_manager::create_interface() +{ + return new tap_interface(); +} + diff --git a/ext/tap-mac/tuntap/src/tap/tap.h b/ext/tap-mac/tuntap/src/tap/tap.h new file mode 100644 index 0000000..a5164d4 --- /dev/null +++ b/ext/tap-mac/tuntap/src/tap/tap.h @@ -0,0 +1,111 @@ +/* + * ethertap device for MacOSX. + */ +/* + * Copyright (c) 2011 Mattias Nissler + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. The name of the author may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __TAP_H__ +#define __TAP_H__ + +#include "tuntap.h" + +extern "C" { + +#include + +} + +#define TAP_FAMILY_NAME ((char *) "zt") +#define TAP_IF_COUNT 32 /* max number of tap interfaces */ +#define TAP_MTU 2800 +#define TAP_LLADDR tap_lladdr + +/* the mac address of our interfaces. note that the last byte will be replaced by the unit number */ +extern u_char tap_lladdr[]; + +/* tap manager */ +class tap_manager : public tuntap_manager { + + protected: + /* just define the interface creation method */ + virtual tuntap_interface *create_interface(); + +}; + +/* the tap network interface */ +class tap_interface : public tuntap_interface { + public: + tap_interface(); + + protected: + /* maximum number of protocols that can be attached */ + static const unsigned int MAX_ATTACHED_PROTOS = 8; + + /* information about attached protocols for demuxing is stored here */ + struct { + /* whether this entry is used */ + bool used; + /* type in the ethernet header */ + u_int16_t type; + /* protocol passed to add_proto */ + protocol_family_t proto; + } attached_protos[MAX_ATTACHED_PROTOS]; + + /* The input thread for the network interface. */ + thread_t input_thread; + + /* initializes the interface */ + virtual bool initialize(unsigned short major, unsigned short unit); + + /* shuts the interface down */ + virtual void shutdown(); + + /* called when the character device is opened in order to intialize the network + * interface. + */ + virtual int initialize_interface(); + /* called when the character device is closed to shutdown the network interface */ + virtual void shutdown_interface(); + + /* override interface routines */ + virtual errno_t if_ioctl(u_int32_t cmd, void *arg); + virtual errno_t if_demux(mbuf_t m, char *header, protocol_family_t *proto); + virtual errno_t if_framer(mbuf_t *m, const struct sockaddr *dest, + const char *dest_linkaddr, const char *frame_type); + virtual errno_t if_add_proto(protocol_family_t proto, + const struct ifnet_demux_desc *ddesc, u_int32_t ndesc); + virtual errno_t if_del_proto(protocol_family_t proto); + virtual errno_t if_check_multi(const struct sockaddr *maddr); + + /* if_add_proto helper */ + errno_t add_one_proto(protocol_family_t proto, const struct ifnet_demux_desc &dd); + + /* generates a kernel event */ + void generate_link_event(u_int32_t code); + + friend class tap_manager; +}; + +#endif /* __TAP_H__ */ + diff --git a/ext/tap-mac/tuntap/src/tuntap.cc b/ext/tap-mac/tuntap/src/tuntap.cc new file mode 100644 index 0000000..d0f8901 --- /dev/null +++ b/ext/tap-mac/tuntap/src/tuntap.cc @@ -0,0 +1,963 @@ +/* + * ip tunnel/ethertap device for MacOSX. Common functionality of tap_interface and tun_interface. + * + * tuntap_interface class definition + */ +/* + * Copyright (c) 2011 Mattias Nissler + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. The name of the author may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tuntap.h" + +#if 0 +#define dprintf(...) log(LOG_INFO, __VA_ARGS__) +#else +#define dprintf(...) +#endif + +extern "C" { + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +} + +extern "C" { + +/* interface service functions that delegate to the appropriate tuntap_interface instance */ +errno_t +tuntap_if_output(ifnet_t ifp, mbuf_t m) +{ + if (ifp != NULL) { + tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); + if (ttif != NULL) + return ttif->if_output(m); + } + + if (m != NULL) + mbuf_freem_list(m); + + return ENODEV; +} + +errno_t +tuntap_if_ioctl(ifnet_t ifp, long unsigned int cmd, void *arg) +{ + if (ifp != NULL) { + tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); + if (ttif != NULL) + return ttif->if_ioctl(cmd, arg); + } + + return ENODEV; +} + +errno_t +tuntap_if_set_bpf_tap(ifnet_t ifp, bpf_tap_mode mode, int (*cb)(ifnet_t, mbuf_t)) +{ + if (ifp != NULL) { + tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); + if (ttif != NULL) + return ttif->if_set_bpf_tap(mode, cb); + } + + return ENODEV; +} + +errno_t +tuntap_if_demux(ifnet_t ifp, mbuf_t m, char *header, protocol_family_t *proto) +{ + if (ifp != NULL) { + tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); + if (ttif != NULL) + return ttif->if_demux(m, header, proto); + } + + return ENODEV; +} + +errno_t +tuntap_if_framer(ifnet_t ifp, mbuf_t *m, const struct sockaddr *dest, const char *dest_linkaddr, + const char *frame_type) +{ + if (ifp != NULL) { + tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); + if (ttif != NULL) + return ttif->if_framer(m, dest, dest_linkaddr, frame_type); + } + + return ENODEV; +} + +errno_t +tuntap_if_add_proto(ifnet_t ifp, protocol_family_t proto, const struct ifnet_demux_desc *ddesc, + u_int32_t ndesc) +{ + if (ifp != NULL) { + tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); + if (ttif != NULL) + return ttif->if_add_proto(proto, ddesc, ndesc); + } + + return ENODEV; +} + +errno_t +tuntap_if_del_proto(ifnet_t ifp, protocol_family_t proto) +{ + if (ifp != NULL) { + tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); + if (ttif != NULL) + return ttif->if_del_proto(proto); + } + + return ENODEV; +} + +errno_t +tuntap_if_check_multi(ifnet_t ifp, const struct sockaddr* maddr) +{ + if (ifp != NULL) + { + tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); + if (ttif != NULL) + return ttif->if_check_multi(maddr); + } + + return ENODEV; +} + +void +tuntap_if_detached(ifnet_t ifp) +{ + if (ifp != NULL) { + tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); + if (ttif != NULL) + ttif->if_detached(); + } +} + +errno_t +tuntap_if_noop_output(ifnet_t, mbuf_t) +{ + return ENODEV; +} + +errno_t +tuntap_if_noop_demux(ifnet_t, mbuf_t, char*, protocol_family_t*) +{ + return ENODEV; +} + +errno_t +tuntap_if_noop_add_proto(ifnet_t, protocol_family_t, const struct ifnet_demux_desc*, u_int32_t) +{ + return ENODEV; +} + +errno_t +tuntap_if_noop_del_proto(ifnet_t, protocol_family_t) +{ + return ENODEV; +} + +} /* extern "C" */ + +/* tuntap_mbuf_queue */ +tuntap_mbuf_queue::tuntap_mbuf_queue() +{ + head = tail = NULL; + size = 0; +} + +tuntap_mbuf_queue::~tuntap_mbuf_queue() +{ + clear(); +} + +bool +tuntap_mbuf_queue::enqueue(mbuf_t mb) +{ + if (size == QUEUE_SIZE) + return false; + + mbuf_setnextpkt(mb, NULL); + + if (head == NULL) + head = tail = mb; + else { + mbuf_setnextpkt(tail, mb); + tail = mb; + } + size++; + + return true; +} + +mbuf_t +tuntap_mbuf_queue::dequeue() +{ + mbuf_t ret; + + /* check wether there is a packet in the queue */ + if (head == NULL) + return NULL; + + /* fetch it */ + ret = head; + head = mbuf_nextpkt(head); + mbuf_setnextpkt(ret, NULL); + size--; + + return ret; +} + +void +tuntap_mbuf_queue::clear() +{ + /* free mbufs that are in the queue */ + if (head != NULL) + mbuf_freem_list(head); + + head = NULL; + tail = NULL; + size = 0; +} + +/* tuntap_interface members */ +tuntap_interface::tuntap_interface() +{ + /* initialize the members */ + ifp = NULL; + open = false; + block_io = true; + dev_handle = NULL; + pid = 0; + selthreadclear(&rsel); + bpf_mode = BPF_MODE_DISABLED; + bpf_callback = NULL; + bzero(unique_id, UIDLEN); + in_ioctl = false; +} + +tuntap_interface::~tuntap_interface() +{ +} + +bool +tuntap_interface::register_chardev(unsigned short major) +{ + /* register character device */ + dev_handle = devfs_make_node(makedev(major, unit), DEVFS_CHAR, 0, 0, 0660, "%s%d", + family_name, (int) unit); + + if (dev_handle == NULL) { + log(LOG_ERR, "tuntap: could not make /dev/%s%d\n", family_name, (int) unit); + return false; + } + + return true; +} + +void +tuntap_interface::unregister_chardev() +{ + dprintf("unregistering character device\n"); + + /* unregister character device */ + if (dev_handle != NULL) + devfs_remove(dev_handle); + dev_handle = NULL; +} + +bool +tuntap_interface::register_interface(const struct sockaddr_dl* lladdr, void *bcaddr, + u_int32_t bcaddrlen) +{ + struct ifnet_init_params ip; + errno_t err; + + dprintf("register_interface\n"); + + /* initialize an initialization info struct */ + ip.uniqueid_len = UIDLEN; + ip.uniqueid = unique_id; + ip.name = family_name; + ip.unit = unit; + ip.family = family; + ip.type = type; + ip.output = tuntap_if_output; + ip.demux = tuntap_if_demux; + ip.add_proto = tuntap_if_add_proto; + ip.del_proto = tuntap_if_del_proto; + ip.check_multi = tuntap_if_check_multi; + ip.framer = tuntap_if_framer; + ip.softc = this; + ip.ioctl = tuntap_if_ioctl; + ip.set_bpf_tap = tuntap_if_set_bpf_tap; + ip.detach = tuntap_if_detached; + ip.event = NULL; + ip.broadcast_addr = bcaddr; + ip.broadcast_len = bcaddrlen; + + dprintf("tuntap: tuntap_if_check_multi is at 0x%08x\n", (void*) tuntap_if_check_multi); + + /* allocate the interface */ + err = ifnet_allocate(&ip, &ifp); + if (err) { + log(LOG_ERR, "tuntap: could not allocate interface for %s%d: %d\n", family_name, + (int) unit, err); + ifp = NULL; + return false; + } + + /* activate the interface */ + err = ifnet_attach(ifp, lladdr); + if (err) { + log(LOG_ERR, "tuntap: could not attach interface %s%d: %d\n", family_name, + (int) unit, err); + ifnet_release(ifp); + ifp = NULL; + return false; + } + + dprintf("setting interface flags\n"); + + /* set interface flags */ + ifnet_set_flags(ifp, IFF_RUNNING | IFF_MULTICAST | IFF_SIMPLEX, (u_int16_t) ~0UL); + + dprintf("flags: %x\n", ifnet_flags(ifp)); + + return true; +} + +void +tuntap_interface::unregister_interface() +{ + errno_t err; + + dprintf("unregistering network interface\n"); + + if (ifp != NULL) { + interface_detached = false; + + /* detach interface */ + err = ifnet_detach(ifp); + if (err) + log(LOG_ERR, "tuntap: error detaching interface %s%d: %d\n", + family_name, unit, err); + + dprintf("interface detaching\n"); + + /* Wait until the interface has completely been detached. */ + thread_sync_lock.lock(); + while (!interface_detached) + thread_sync_lock.sleep(&interface_detached); + thread_sync_lock.unlock(); + + dprintf("interface detached\n"); + + /* release the interface */ + ifnet_release(ifp); + + ifp = NULL; + } + + dprintf("network interface unregistered\n"); +} + +void +tuntap_interface::cleanup_interface() +{ + errno_t err; + ifaddr_t *addrs; + ifaddr_t *a; + struct ifreq ifr; + + /* mark the interface down */ + ifnet_set_flags(ifp, 0, IFF_UP | IFF_RUNNING); + + /* Unregister all interface addresses. This works around a deficiency in the Darwin kernel. + * If we don't remove all IP addresses that are attached to the interface it can happen that + * the IP code fails to clean them up itself. When the interface is recycled, the IP code + * might then think some addresses are still attached to the interface... + */ + + err = ifnet_get_address_list(ifp, &addrs); + if (!err) { + + /* Execute a SIOCDIFADDR ioctl for each address. For technical reasons, we can only + * do that with a socket of the appropriate family. So try to create a dummy socket. + * I know this is a little expensive, but better than crashing... + * + * This really sucks. + */ + for (a = addrs; *a != NULL; a++) { + /* initialize the request parameters */ + snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d", + ifnet_name(ifp), ifnet_unit(ifp)); + ifaddr_address(*a, &(ifr.ifr_addr), sizeof(ifr.ifr_addr)); + if (ifr.ifr_addr.sa_family != AF_INET) + continue; + + dprintf("trying to delete address of family %d\n", ifr.ifr_addr.sa_family); + + do_sock_ioctl(ifr.ifr_addr.sa_family, SIOCDIFADDR, &ifr); + } + + /* release the address list */ + ifnet_free_address_list(addrs); + } +} + +bool +tuntap_interface::idle() +{ + return !(open); +} + +void +tuntap_interface::notify_bpf(mbuf_t mb, bool out) +{ + auto_lock l(&bpf_lock); + + if ((out && bpf_mode == BPF_MODE_OUTPUT) + || (!out && bpf_mode == BPF_MODE_INPUT) + || (bpf_mode == BPF_MODE_INPUT_OUTPUT)) + (*bpf_callback)(ifp, mb); +} + +void +tuntap_interface::do_sock_ioctl(sa_family_t af, unsigned long cmd, void* arg) { + if (in_ioctl) { + log(LOG_ERR, "tuntap: ioctl recursion detected, aborting.\n"); + return; + } + + socket_t sock; + errno_t err = sock_socket(af, SOCK_RAW, 0, NULL, NULL, &sock); + if (err) { + log(LOG_ERR, "tuntap: failed to create socket: %d\n", err); + return; + } + + in_ioctl = true; + + /* issue the ioctl */ + err = sock_ioctl(sock, cmd, arg); + if (err) + log(LOG_ERR, "tuntap: socket ioctl %d failed: %d\n", cmd, err); + + in_ioctl = false; + + /* get rid of the socket */ + sock_close(sock); +} + +/* character device service methods */ +int +tuntap_interface::cdev_open(int flags, int devtype, proc_t p) +{ + dprintf("tuntap: cdev_open()\n"); + + /* grab the lock so that there can only be one thread inside */ + auto_lock l(&lock); + + /* check wether it is already open */ + if (open) + return EBUSY; + + /* bring the network interface up */ + int error = initialize_interface(); + if (error) + return error; + + open = true; + pid = proc_pid(p); + + return 0; +} + +int +tuntap_interface::cdev_close(int flags, int devtype, proc_t p) +{ + dprintf("tuntap: cdev_close()\n"); + + auto_lock l(&lock); + + if (open) { + open = false; + + /* shut down the network interface */ + shutdown_interface(); + + /* clear the queue */ + send_queue.clear(); + + /* wakeup the cdev thread and notify selects */ + wakeup(this); + selwakeup(&rsel); + + return 0; + } + + return EBADF; +} + +int +tuntap_interface::cdev_read(uio_t uio, int ioflag) +{ + auto_lock l(&lock); + + unsigned int nb = 0; + int error; + + dprintf("tuntap: cdev read\n"); + + if (!open || ifp == NULL || !(ifnet_flags(ifp) & IFF_UP)) + return EIO; + + /* fetch a new mbuf from the queue if necessary */ + mbuf_t cur_mbuf = NULL; + while (cur_mbuf == NULL) { + dprintf("tuntap: fetching new mbuf\n"); + + cur_mbuf = send_queue.dequeue(); + if (cur_mbuf == NULL) { + /* nothing in queue, block or return */ + if (!block_io) { + dprintf("tuntap: aborting (nbio)\n"); + return EWOULDBLOCK; + } else { + /* block */ + dprintf("tuntap: waiting\n"); + /* release the lock while waiting */ + l.unlock(); + error = msleep(this, NULL, PZERO | PCATCH, "tuntap", NULL); + + l.lock(); + + if (error) + return error; + + /* see whether the device was closed in the meantime */ + if (!open || ifp == NULL || !(ifnet_flags(ifp) & IFF_UP)) + return EIO; + + } + } + } + + /* notify bpf */ + notify_bpf(cur_mbuf, true); + + /* output what we have */ + do { + dprintf("tuntap: got new mbuf: %p uio_resid: %d\n", cur_mbuf, uio_resid(uio)); + + /* now we have an mbuf */ + int chunk_len = min(mbuf_len(cur_mbuf), uio_resid(uio)); + error = uiomove((char *) mbuf_data(cur_mbuf), chunk_len, uio); + if (error) { + mbuf_freem(cur_mbuf); + return error; + } + nb += chunk_len; + + dprintf("tuntap: moved %d bytes to userspace uio_resid: %d\n", chunk_len, + uio_resid(uio)); + + /* update cur_mbuf */ + cur_mbuf = mbuf_free(cur_mbuf); + + } while (uio_resid(uio) > 0 && cur_mbuf != NULL); + + /* update statistics */ + ifnet_stat_increment_out(ifp, 1, nb, 0); + + /* still data left? forget about that ;-) */ + if (cur_mbuf != NULL) + mbuf_freem(cur_mbuf); + + dprintf("tuntap: read done\n"); + + return 0; +} + +int +tuntap_interface::cdev_write(uio_t uio, int ioflag) +{ + auto_lock l(&lock); + + if (!open || ifp == NULL || !(ifnet_flags(ifp) & IFF_UP)) + return EIO; + + dprintf("tuntap: cdev write. uio_resid: %d\n", uio_resid(uio)); + + /* pack the data into an mbuf chain */ + mbuf_t first, mb; + + /* first we need an mbuf having a header */ + mbuf_gethdr(MBUF_WAITOK, MBUF_TYPE_DATA, &first); + if (first == NULL) { + log(LOG_ERR, "tuntap: could not get mbuf.\n"); + return ENOMEM; + } + mbuf_setlen(first, 0); + + unsigned int mlen = mbuf_maxlen(first); + unsigned int chunk_len; + unsigned int copied = 0; + unsigned int max_data_len = ifnet_mtu(ifp) + ifnet_hdrlen(ifp); + int error; + + /* stuff the data into the mbuf(s) */ + mb = first; + while (uio_resid(uio) > 0) { + /* copy a chunk. enforce mtu (don't know if this is correct behaviour) */ + chunk_len = min(max_data_len - copied, min(uio_resid(uio), mlen)); + error = uiomove((caddr_t) mbuf_data(mb), chunk_len, uio); + if (error) { + log(LOG_ERR, "tuntap: could not copy data from userspace: %d\n", error); + mbuf_freem(first); + return error; + } + + dprintf("tuntap: copied %d bytes, uio_resid %d\n", chunk_len, + uio_resid(uio)); + + mlen -= chunk_len; + mbuf_setlen(mb, mbuf_len(mb) + chunk_len); + copied += chunk_len; + + /* if done, break the loop */ + if (uio_resid(uio) <= 0 || copied >= max_data_len) + break; + + /* allocate a new mbuf if the current is filled */ + if (mlen == 0) { + mbuf_t next; + mbuf_get(MBUF_WAITOK, MBUF_TYPE_DATA, &next); + if (next == NULL) { + log(LOG_ERR, "tuntap: could not get mbuf.\n"); + mbuf_freem(first); + return ENOMEM; + } + mbuf_setnext(mb, next); + mb = next; + mbuf_setlen(mb, 0); + mlen = mbuf_maxlen(mb); + } + } + + /* fill in header info */ + mbuf_pkthdr_setrcvif(first, ifp); + mbuf_pkthdr_setlen(first, copied); + mbuf_pkthdr_setheader(first, mbuf_data(first)); + mbuf_set_csum_performed(first, 0, 0); + + /* update statistics */ + ifnet_stat_increment_in(ifp, 1, copied, 0); + + dprintf("tuntap: mbuf chain constructed. first: %p mb: %p len: %d data: %p\n", + first, mb, mbuf_len(first), mbuf_data(first)); + + /* notify bpf */ + notify_bpf(first, false); + + /* need to adjust the data pointer to point directly behind the linklevel header. The header + * itself is later accessed via m_pkthdr.header. Well, if something is ugly, here is it. + */ + mbuf_adj(first, ifnet_hdrlen(ifp)); + + /* pass the packet over to the network stack */ + error = ifnet_input(ifp, first, NULL); + + if (error) { + log(LOG_ERR, "tuntap: could not input packet into network stack.\n"); + mbuf_freem(first); + return error; + } + + return 0; +} + +int +tuntap_interface::cdev_ioctl(u_long cmd, caddr_t data, int fflag, proc_t p) +{ + auto_lock l(&lock); + + dprintf("tuntap: cdev ioctl: %d\n", (int) (cmd & 0xff)); + + switch (cmd) { + case FIONBIO: + /* set i/o mode */ + block_io = *((int *) data) ? false : true; + return 0; + case FIOASYNC: + /* don't allow switching it on */ + if (*((int *) data)) + return ENOTTY; + return 0; + } + + return ENOTTY; +} + +int +tuntap_interface::cdev_select(int which, void *wql, proc_t p) +{ + auto_lock l(&lock); + + int ret = 0; + + dprintf("tuntap: select. which: %d\n", which); + + switch (which) { + case FREAD: + /* check wether data is available */ + { + if (!send_queue.empty()) + ret = 1; + else { + dprintf("tuntap: select: waiting\n"); + selrecord(p, &rsel, wql); + } + } + break; + case FWRITE: + /* we are always writeable */ + ret = 1; + } + + return ret; +} + +/* interface service methods */ +errno_t +tuntap_interface::if_output(mbuf_t m) +{ + mbuf_t pkt; + + dprintf("tuntap: if output\n"); + + /* just to be sure */ + if (m == NULL) + return 0; + + if (!open || ifp == NULL || !(ifnet_flags(ifp) & IFF_UP)) { + mbuf_freem_list(m); + return EHOSTDOWN; + } + + /* check whether packet has a header */ + if ((mbuf_flags(m) & MBUF_PKTHDR) == 0) { + log(LOG_ERR, "tuntap: packet to be output has no mbuf header.\n"); + mbuf_freem_list(m); + return EINVAL; + } + + /* put the packet(s) into the output queue */ + while (m != NULL) { + /* keep pointer, iterate */ + pkt = m; + m = mbuf_nextpkt(m); + mbuf_setnextpkt(pkt, NULL); + + auto_lock l(&lock); + + if (!send_queue.enqueue(pkt)) { + mbuf_freem(pkt); + mbuf_freem_list(m); + return ENOBUFS; + } + } + + /* protect the wakeup calls with the lock, not sure they are safe. */ + { + auto_lock l(&lock); + + /* wakeup the cdev thread and notify selects */ + wakeup(this); + selwakeup(&rsel); + } + + return 0; +} + +errno_t +tuntap_interface::if_ioctl(u_int32_t cmd, void *arg) +{ + dprintf("tuntap: if ioctl: %d\n", (int) (cmd & 0xff)); + + switch (cmd) { + case SIOCSIFADDR: + { + dprintf("tuntap: if_ioctl: SIOCSIFADDR\n"); + + /* Unfortunately, ifconfig sets the address family field of an INET + * netmask to zero, which makes early mDNSresponder versions ignore + * the interface. Fix that here. This one is of the category "ugly + * workaround". Dumb Darwin... + * + * Meanwhile, Apple has fixed mDNSResponder, and recent versions of + * Leopard don't need this hack anymore. However, Tiger still has a + * broken version so we leave the hack in for now. + * + * TODO: Revisit when dropping Tiger support. + * + * Btw. If you configure other network interfaces using ifconfig, + * you run into the same problem. I still don't know how to make the + * tap devices show up in the network configuration panel... + */ + ifaddr_t ifa = (ifaddr_t) arg; + if (ifa == NULL) + return 0; + + sa_family_t af = ifaddr_address_family(ifa); + if (af != AF_INET) + return 0; + + struct ifaliasreq ifra; + int sa_size = sizeof(struct sockaddr); + if (ifaddr_address(ifa, &ifra.ifra_addr, sa_size) + || ifaddr_dstaddress(ifa, &ifra.ifra_broadaddr, sa_size) + || ifaddr_netmask(ifa, &ifra.ifra_mask, sa_size)) { + log(LOG_WARNING, + "tuntap: failed to parse interface address.\n"); + return 0; + } + + // Check that the address family fields match. If not, issue another + // SIOCAIFADDR to fix the entry. + if (ifra.ifra_addr.sa_family != af + || ifra.ifra_broadaddr.sa_family != af + || ifra.ifra_mask.sa_family != af) { + log(LOG_INFO, "tuntap: Fixing address family for %s%d\n", + family_name, unit); + + snprintf(ifra.ifra_name, sizeof(ifra.ifra_name), "%s%d", + family_name, unit); + ifra.ifra_addr.sa_family = af; + ifra.ifra_broadaddr.sa_family = af; + ifra.ifra_mask.sa_family = af; + + do_sock_ioctl(af, SIOCAIFADDR, &ifra); + } + + return 0; + } + + case SIOCSIFFLAGS: + return 0; + + case SIOCGIFSTATUS: + { + struct ifstat *stat = (struct ifstat *) arg; + int len; + char *p; + + if (stat == NULL) + return EINVAL; + + /* print status */ + len = strlen(stat->ascii); + p = stat->ascii + len; + if (open) { + snprintf(p, IFSTATMAX - len, "\topen (pid %u)\n", pid); + } else { + snprintf(p, IFSTATMAX - len, "\tclosed\n"); + } + + return 0; + } + + case SIOCSIFMTU: + { + struct ifreq *ifr = (struct ifreq *) arg; + + if (ifr == NULL) + return EINVAL; + + ifnet_set_mtu(ifp, ifr->ifr_mtu); + + return 0; + } + + case SIOCDIFADDR: + return 0; + + } + + return EOPNOTSUPP; +} + +errno_t +tuntap_interface::if_set_bpf_tap(bpf_tap_mode mode, int (*cb)(ifnet_t, mbuf_t)) +{ + dprintf("tuntap: mode %d\n", mode); + + auto_lock l(&bpf_lock); + + bpf_callback = cb; + bpf_mode = mode; + + return 0; +} + +errno_t +tuntap_interface::if_check_multi(const struct sockaddr *maddr) +{ + dprintf("tuntap: if_check_multi\n"); + + return EOPNOTSUPP; +} + +void +tuntap_interface::if_detached() +{ + dprintf("tuntap: if_detached\n"); + + /* wake unregister_interface() */ + thread_sync_lock.lock(); + interface_detached = true; + thread_sync_lock.wakeup(&interface_detached); + thread_sync_lock.unlock(); + + dprintf("if_detached done\n"); +} + diff --git a/ext/tap-mac/tuntap/src/tuntap.h b/ext/tap-mac/tuntap/src/tuntap.h new file mode 100644 index 0000000..d5f398d --- /dev/null +++ b/ext/tap-mac/tuntap/src/tuntap.h @@ -0,0 +1,301 @@ +/* + * ip tunnel/ethertap device for MacOSX. + * + * The class tuntaptap_interface contains the common functionality of tuntap_interface and + * tap_interface. + */ +/* + * Copyright (c) 2011 Mattias Nissler + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. The name of the author may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __TUNTAP_H__ +#define __TUNTAP_H__ + +#include "util.h" +#include "lock.h" + +extern "C" { + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +} + +extern "C" { + +errno_t tuntap_if_output(ifnet_t ifp, mbuf_t m); +errno_t tuntap_if_ioctl(ifnet_t ifp, long unsigned int cmd, void *arg); +errno_t tuntap_if_set_bpf_tap(ifnet_t ifp, bpf_tap_mode mode, int (*cb)(ifnet_t, mbuf_t)); +errno_t tuntap_if_demux(ifnet_t ifp, mbuf_t m, char *header, protocol_family_t *proto); +errno_t tuntap_if_framer(ifnet_t ifp, mbuf_t *m, const struct sockaddr *dest, + const char *dest_linkaddr, const char *frame_type); +errno_t tuntap_if_add_proto(ifnet_t ifp, protocol_family_t proto, + const struct ifnet_demux_desc *ddesc, u_int32_t ndesc); +errno_t tuntap_if_del_proto(ifnet_t ifp, protocol_family_t proto); +errno_t tuntap_if_check_multi(ifnet_t ifp, const struct sockaddr *maddr); +void tuntap_if_detached(ifnet_t ifp); + +} + +/* forward declaration */ +class tuntap_interface; + +/* both interface families have their manager object that will create, initialize, shutdown and + * delete interfaces. This is (mostly) generic so it can be used both for tun and tap. The only + * exception is the interface creation, therefore this class is abstract. tun and tap have their own + * versions that simply fill in create_interface(). + */ +class tuntap_manager { + + protected: + /* manager cdev gate */ + tt_gate cdev_gate; + /* interface count */ + unsigned int count; + /* an array holding all the interface instances */ + tuntap_interface **tuntaps; + /* the major device number */ + int dev_major; + /* family name */ + char *family; + + /* wether static members are initialized */ + static bool statics_initialized; + + /* major-to-manager-map */ + static const int MAX_CDEV = 256; + static tuntap_manager *mgr_map[MAX_CDEV]; + + /* initializes static members */ + void initialize_statics(); + + public: + /* sets major device number, allocates the interface table. */ + bool initialize(unsigned int count, char *family); + + /* tries to shutdown the family. returns true if successful. the manager object may + * not be deleted if this wasn't called successfully. + */ + bool shutdown(); + + /* the destructor deletes allocated memory and unregisters the character device + * switch */ + virtual ~tuntap_manager(); + + /* here are the cdev routines for the class. They will figure out the manager object + * and call the service methods declared below. + */ + static int cdev_open(dev_t dev, int flags, int devtype, proc_t p); + static int cdev_close(dev_t dev, int flags, int devtype, proc_t p); + static int cdev_read(dev_t dev, uio_t uio, int ioflag); + static int cdev_write(dev_t dev, uio_t uio, int ioflag); + static int cdev_ioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, + proc_t p); + static int cdev_select(dev_t dev, int which, void *wql, proc_t p); + + protected: + /* Here are the actual service routines that will do the required things (creating + * interfaces and such) and forward to the interface's implementation. + */ + int do_cdev_open(dev_t dev, int flags, int devtype, proc_t p); + int do_cdev_close(dev_t dev, int flags, int devtype, proc_t p); + int do_cdev_read(dev_t dev, uio_t uio, int ioflag); + int do_cdev_write(dev_t dev, uio_t uio, int ioflag); + int do_cdev_ioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, proc_t p); + int do_cdev_select(dev_t dev, int which, void *wql, proc_t p); + + /* abstract method that will create an interface. Implemented by tun and tap */ + virtual tuntap_interface *create_interface() = 0; + + /* makes sure there is one idle interface available (if nothing fails */ + void ensure_idle_device(); + +}; + +/* a class implementing a mbuf packet queue. On Darwin 7 we had struct ifqueue, but that is now + * internal to the kernel for Darwin 8. So lets have our own. + */ +class tuntap_mbuf_queue { + + private: + /* output end of the queue. dequeueing takes mbufs from here */ + mbuf_t head; + /* input end. new mbufs are appended here. */ + mbuf_t tail; + + /* size */ + unsigned int size; + + /* maximum queue size */ + static const unsigned int QUEUE_SIZE = 128; + + public: + /* initialize new empty queue */ + tuntap_mbuf_queue(); + ~tuntap_mbuf_queue(); + + /* is the queue full? */ + bool full() { return size == QUEUE_SIZE; } + /* is it emtpy? */ + bool empty() { return size == 0; } + + /* enqueue an mbuf. returns true if there was space left, so the mbuf could be + * queued, false otherwise */ + bool enqueue(mbuf_t mb); + + /* tries to dequeue the next mbuf. If the queue is empty, NULL is returned */ + mbuf_t dequeue(); + + /* makes the queue empty, discarding any queue packets */ + void clear(); +}; + +class tuntap_interface { + + protected: + /* interface number */ + unsigned int unit; + /* family name */ + char *family_name; + /* family identifier */ + ifnet_family_t family; + /* interface type */ + u_int32_t type; + /* id string */ + static const unsigned int UIDLEN = 20; + char unique_id[UIDLEN]; + + /* synchronization */ + tt_mutex lock; + tt_mutex bpf_lock; + tt_mutex thread_sync_lock; + + /* the interface structure registered */ + ifnet_t ifp; + /* whether the device has been opened */ + bool open; + /* whether we are doing blocking i/o */ + bool block_io; + /* whether the interface has properly been detached */ + bool interface_detached; + /* handle to the devfs node for the character device */ + void *dev_handle; + /* the pid of the process that opened the cdev, if any */ + pid_t pid; + /* read select info */ + struct selinfo rsel; + /* bpf mode, wether filtering is on or off */ + bpf_tap_mode bpf_mode; + /* bpf callback. called when packet arrives/leaves */ + int (*bpf_callback)(ifnet_t, mbuf_t); + /* pending packets queue (for output), must be accessed with the lock held */ + tuntap_mbuf_queue send_queue; + /* whether an ioctl that we issued is currently being processed */ + bool in_ioctl; + + /* protected constructor. initializes most of the members */ + tuntap_interface(); + virtual ~tuntap_interface(); + + /* initialize the device */ + virtual bool initialize(unsigned short major, unsigned short unit) = 0; + + /* character device management */ + virtual bool register_chardev(unsigned short major); + virtual void unregister_chardev(); + + /* network interface management */ + virtual bool register_interface(const struct sockaddr_dl *lladdr, + void *bcaddr, u_int32_t bcaddrlen); + virtual void unregister_interface(); + virtual void cleanup_interface(); + + /* called when the character device is opened in order to intialize the network + * interface. + */ + virtual int initialize_interface() = 0; + /* called when the character device is closed to shutdown the network interface */ + virtual void shutdown_interface() = 0; + + /* check wether the interface is idle (so it can be brought down) */ + virtual bool idle(); + + /* shut it down */ + virtual void shutdown() = 0; + + /* notifies BPF of a packet coming through */ + virtual void notify_bpf(mbuf_t mb, bool out); + + /* executes a socket ioctl through a temporary socket */ + virtual void do_sock_ioctl(sa_family_t af, unsigned long cmd, void* arg); + + /* character device service methods. Called by the manager */ + virtual int cdev_open(int flags, int devtype, proc_t p); + virtual int cdev_close(int flags, int devtype, proc_t p); + virtual int cdev_read(uio_t uio, int ioflag); + virtual int cdev_write(uio_t uio, int ioflag); + virtual int cdev_ioctl(u_long cmd, caddr_t data, int fflag, proc_t p); + virtual int cdev_select(int which, void *wql, proc_t p); + + /* interface functions. friends and implementation methods */ + friend errno_t tuntap_if_output(ifnet_t ifp, mbuf_t m); + friend errno_t tuntap_if_ioctl(ifnet_t ifp, long unsigned int cmd, void *arg); + friend errno_t tuntap_if_set_bpf_tap(ifnet_t ifp, bpf_tap_mode mode, + int (*cb)(ifnet_t, mbuf_t)); + friend errno_t tuntap_if_demux(ifnet_t ifp, mbuf_t m, char *header, + protocol_family_t *proto); + friend errno_t tuntap_if_framer(ifnet_t ifp, mbuf_t *m, const struct sockaddr *dest, + const char *dest_linkaddr, const char *frame_type); + friend errno_t tuntap_if_add_proto(ifnet_t ifp, protocol_family_t proto, + const struct ifnet_demux_desc *ddesc, u_int32_t ndesc); + friend errno_t tuntap_if_del_proto(ifnet_t ifp, protocol_family_t proto); + friend errno_t tuntap_if_check_multi(ifnet_t ifp, const struct sockaddr *maddr); + friend void tuntap_if_detached(ifnet_t ifp); + + virtual errno_t if_output(mbuf_t m); + virtual errno_t if_ioctl(u_int32_t cmd, void *arg); + virtual errno_t if_set_bpf_tap(bpf_tap_mode mode, int (*cb)(ifnet_t, mbuf_t)); + virtual errno_t if_demux(mbuf_t m, char *header, protocol_family_t *proto) = 0; + virtual errno_t if_framer(mbuf_t *m, const struct sockaddr *dest, + const char *dest_linkaddr, const char *frame_type) = 0; + virtual errno_t if_add_proto(protocol_family_t proto, + const struct ifnet_demux_desc *ddesc, u_int32_t ndesc) = 0; + virtual errno_t if_del_proto(protocol_family_t proto) = 0; + virtual errno_t if_check_multi(const struct sockaddr *maddr); + virtual void if_detached(); + + /* tuntap_manager feeds us with cdev input, so it is our friend */ + friend class tuntap_manager; +}; + +#endif /* __TUNTAP_H__ */ + diff --git a/ext/tap-mac/tuntap/src/tuntap_mgr.cc b/ext/tap-mac/tuntap/src/tuntap_mgr.cc new file mode 100644 index 0000000..f41394e --- /dev/null +++ b/ext/tap-mac/tuntap/src/tuntap_mgr.cc @@ -0,0 +1,372 @@ +/* + * ip tunnel/ethertap device for MacOSX. + * + * tuntap_manager definition. + */ +/* + * Copyright (c) 2011 Mattias Nissler + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. The name of the author may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tuntap.h" +#include "mem.h" + +extern "C" { + +#include +#include +#include +#include + +#include + +#include + +} + +#if 0 +#define dprintf(...) log(LOG_INFO, __VA_ARGS__) +#else +#define dprintf(...) +#endif + +/* cdevsw for tuntap_manager */ +static struct cdevsw mgr_cdevsw = +{ + tuntap_manager::cdev_open, + tuntap_manager::cdev_close, + tuntap_manager::cdev_read, + tuntap_manager::cdev_write, + tuntap_manager::cdev_ioctl, + eno_stop, + eno_reset, + NULL, + tuntap_manager::cdev_select, + eno_mmap, + eno_strat, + eno_getc, + eno_putc, + 0 +}; + +/* tuntap_manager members */ +tuntap_manager *tuntap_manager::mgr_map[MAX_CDEV]; + +bool tuntap_manager::statics_initialized = false; + +/* static initializer */ +void +tuntap_manager::initialize_statics() +{ + dprintf("initializing mgr_map\n"); + + /* initialize the major-to-manager map */ + for (int i = 0; i < MAX_CDEV; i++) + mgr_map[i] = NULL; + + statics_initialized = true; +} + +bool +tuntap_manager::initialize(unsigned int count, char *family) +{ + this->count = count; + this->family = family; + this->tuntaps = NULL; + + if (!statics_initialized) + initialize_statics(); + + /* make sure noone can access the character devices until we are done */ + auto_lock l(&cdev_gate); + + /* register the switch for the tap character devices */ + dev_major = cdevsw_add(-1, &mgr_cdevsw); + if (dev_major == -1) { + log(LOG_ERR, "%s: could not register character device switch.\n", family); + return false; + } + + /* allocate memory for the interface instance table */ + tuntaps = (tuntap_interface **) mem_alloc(count * sizeof(tuntap_interface *)); + if (tuntaps == NULL) + { + log(LOG_ERR, "%s: no memory!\n", family); + return false; + } + + bzero(tuntaps, count * sizeof(tuntap_interface *)); + + /* Create the interfaces. This will only add the character devices. The network devices will + * be created upon open()ing the corresponding character devices. + */ + for (int i = 0; i < (int) count; i++) + { + tuntaps[i] = create_interface(); + + if (tuntaps[i] != NULL) + { + if (tuntaps[i]->initialize(dev_major, i)) + { + continue; + } + + /* error here. current interface needs to be shut down */ + i++; + } + + /* something went wrong. clean up. */ + while (--i >= 0) + { + tuntaps[i]->shutdown(); + delete tuntaps[i]; + } + + return false; + } + + /* register the new family in the mgr switch */ + mgr_map[dev_major] = this; + + log(LOG_INFO, "%s kernel extension version %s \n", + family, TUNTAP_VERSION); + + return true; +} + +bool +tuntap_manager::shutdown() +{ + bool ok = true; + + /* we halt the whole thing while we check whether we can shutdown */ + auto_lock l(&cdev_gate); + + /* anyone in? */ + if (cdev_gate.is_anyone_in()) { + dprintf("tuntap_mgr: won't shutdown, threads still behind the gate."); + ok = false; + } else { + /* query the interfaces to see if shutting down is ok */ + if (tuntaps != NULL) { + for (unsigned int i = 0; i < count; i++) { + if (tuntaps[i] != NULL) + ok &= tuntaps[i]->idle(); + } + + /* if yes, do it now */ + if (ok) { + for (unsigned int i = 0; i < count; i++) { + if (tuntaps[i] != NULL) { + tuntaps[i]->shutdown(); + delete tuntaps[i]; + tuntaps[i] = NULL; + } + } + } + } + } + + /* unregister the character device switch */ + if (ok) { + if (dev_major != -1 && cdevsw_remove(dev_major, &mgr_cdevsw) == -1) { + log(LOG_WARNING, + "%s: character device switch got lost. strange.\n", family); + } + mgr_map[dev_major] = NULL; + dev_major = -1; + + /* at this point there is still a chance that some thread hangs at the cdev_gate in + * one of the cdev service functions. I can't imagine any way that would aviod this. + * So lets unblock the gate such that they fail. + */ + unsigned int old_number; + do { + old_number = cdev_gate.get_ticket_number(); + + dprintf("tuntap_manager: waiting for other threads to give up.\n"); + + /* wait one second */ + cdev_gate.sleep(&cdev_gate, 1000000); + + } while (cdev_gate.get_ticket_number() != old_number); + + /* I hope it is safe to unload now. */ + + } else { + log(LOG_WARNING, "%s: won't unload, at least one interface is busy.\n", family); + } + + dprintf("tuntap manager: shutdown %s\n", ok ? "ok" : "failed"); + + return ok; +} + +tuntap_manager::~tuntap_manager() +{ + dprintf("freeing interface table\n"); + + /* free memory */ + if (tuntaps != NULL) + mem_free(tuntaps, count * sizeof(tuntap_interface *)); +} + +/* service method dispatchers */ +int +tuntap_manager::cdev_open(dev_t dev, int flags, int devtype, proc_t p) +{ + return (mgr_map[major(dev)] == NULL ? ENOENT + : mgr_map[major(dev)]->do_cdev_open(dev, flags, devtype, p)); +} + +int +tuntap_manager::cdev_close(dev_t dev, int flags, int devtype, proc_t p) +{ + return (mgr_map[major(dev)] == NULL ? EBADF + : mgr_map[major(dev)]->do_cdev_close(dev, flags, devtype, p)); +} + +int +tuntap_manager::cdev_read(dev_t dev, uio_t uio, int ioflag) +{ + return (mgr_map[major(dev)] == NULL ? EBADF + : mgr_map[major(dev)]->do_cdev_read(dev, uio, ioflag)); +} + +int +tuntap_manager::cdev_write(dev_t dev, uio_t uio, int ioflag) +{ + return (mgr_map[major(dev)] == NULL ? EBADF + : mgr_map[major(dev)]->do_cdev_write(dev, uio, ioflag)); +} + +int +tuntap_manager::cdev_ioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, proc_t p) +{ + return (mgr_map[major(dev)] == NULL ? EBADF + : mgr_map[major(dev)]->do_cdev_ioctl(dev, cmd, data, fflag, p)); +} + +int +tuntap_manager::cdev_select(dev_t dev, int which, void *wql, proc_t p) +{ + return (mgr_map[major(dev)] == NULL ? EBADF + : mgr_map[major(dev)]->do_cdev_select(dev, which, wql, p)); +} + +/* character device service methods */ +int +tuntap_manager::do_cdev_open(dev_t dev, int flags, int devtype, proc_t p) +{ + int dmin = minor(dev); + int error = ENOENT; + + cdev_gate.enter(); + + if (dmin < (int) count && dmin >= 0 && tuntaps[dmin] != NULL) + error = tuntaps[dmin]->cdev_open(flags, devtype, p); + + cdev_gate.exit(); + + return error; +} + +int +tuntap_manager::do_cdev_close(dev_t dev, int flags, int devtype, proc_t p) +{ + int dmin = minor(dev); + int error = EBADF; + + cdev_gate.enter(); + + if (dmin < (int) count && dmin >= 0 && tuntaps[dmin] != NULL) + error = tuntaps[dmin]->cdev_close(flags, devtype, p); + + cdev_gate.exit(); + + return error; +} + +int +tuntap_manager::do_cdev_read(dev_t dev, uio_t uio, int ioflag) +{ + int dmin = minor(dev); + int error = EBADF; + + cdev_gate.enter(); + + if (dmin < (int) count && dmin >= 0 && tuntaps[dmin] != NULL) + error = tuntaps[dmin]->cdev_read(uio, ioflag); + + cdev_gate.exit(); + + return error; +} + +int +tuntap_manager::do_cdev_write(dev_t dev, uio_t uio, int ioflag) +{ + int dmin = minor(dev); + int error = EBADF; + + cdev_gate.enter(); + + if (dmin < (int) count && dmin >= 0 && tuntaps[dmin] != NULL) + error = tuntaps[dmin]->cdev_write(uio, ioflag); + + cdev_gate.exit(); + + return error; +} + +int +tuntap_manager::do_cdev_ioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, proc_t p) +{ + int dmin = minor(dev); + int error = EBADF; + + cdev_gate.enter(); + + if (dmin < (int) count && dmin >= 0 && tuntaps[dmin] != NULL) + error = tuntaps[dmin]->cdev_ioctl(cmd, data, fflag, p); + + cdev_gate.exit(); + + return error; +} + +int +tuntap_manager::do_cdev_select(dev_t dev, int which, void *wql, proc_t p) +{ + int dmin = minor(dev); + int error = EBADF; + + cdev_gate.enter(); + + if (dmin < (int) count && dmin >= 0 && tuntaps[dmin] != NULL) + error = tuntaps[dmin]->cdev_select(which, wql, p); + + cdev_gate.exit(); + + return error; +} + diff --git a/ext/tap-mac/tuntap/src/util.h b/ext/tap-mac/tuntap/src/util.h new file mode 100644 index 0000000..0f6955e --- /dev/null +++ b/ext/tap-mac/tuntap/src/util.h @@ -0,0 +1,46 @@ +/* + * ip tunnel/ethertap device for MacOSX. + * + * Some utilities and misc stuff. + */ +/* + * Copyright (c) 2011 Mattias Nissler + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. The name of the author may not be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __UTIL_H__ +#define __UTIL_H__ + +extern "C" { + +/* In Darwin 8 (OS X Tiger) there is a problem with struct selinfo. It was made `private' to the + * kernel, so its definition is not available from the headers in Kernel.framework. However, we need + * to declare something :-( + */ +struct selinfo { + char data[128]; /* should be enough... */ +}; + +} /* extern "C" */ + +#endif /* __UTIL_H__ */ + diff --git a/ext/x64-salsa2012-asm/README.md b/ext/x64-salsa2012-asm/README.md new file mode 100644 index 0000000..a69a1a6 --- /dev/null +++ b/ext/x64-salsa2012-asm/README.md @@ -0,0 +1,6 @@ +Blazingly fast X64 ASM implementation of Salsa20/12 +====== + +This is ripped from the [cnacl](https://github.com/cjdelisle/cnacl) source. The actual code is by Danial J. Bernstein and is in the public domain. + +This is included on Linux and Mac 64-bit builds and is significantly faster than the SSE intrinsics or C versions. It's used for packet encode/decode only since its use differs a bit from the regular Salsa20 C++ class. Specifically it lacks the ability to be called on multiple blocks, preferring instead to take a key and a single stream to encrypt and that's it. diff --git a/ext/x64-salsa2012-asm/salsa2012.h b/ext/x64-salsa2012-asm/salsa2012.h new file mode 100644 index 0000000..73e375e --- /dev/null +++ b/ext/x64-salsa2012-asm/salsa2012.h @@ -0,0 +1,16 @@ +#ifndef ZT_X64_SALSA2012_ASM +#define ZT_X64_SALSA2012_ASM + +#ifdef __cplusplus +extern "C" { +#endif + +// Generates Salsa20/12 key stream +// output, outlen, nonce, key (256-bit / 32-byte) +extern int zt_salsa2012_amd64_xmm6(unsigned char *, unsigned long long, const unsigned char *, const unsigned char *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/x64-salsa2012-asm/salsa2012.s b/ext/x64-salsa2012-asm/salsa2012.s new file mode 100644 index 0000000..699c89a --- /dev/null +++ b/ext/x64-salsa2012-asm/salsa2012.s @@ -0,0 +1,4488 @@ +# qhasm: enter zt_salsa2012_amd64_xmm6 +.text +.p2align 5 +.globl _zt_salsa2012_amd64_xmm6 +.globl zt_salsa2012_amd64_xmm6 +_zt_salsa2012_amd64_xmm6: +zt_salsa2012_amd64_xmm6: +mov %rsp,%r11 +and $31,%r11 +add $480,%r11 +sub %r11,%rsp + +# qhasm: r11_stack = r11_caller +# asm 1: movq r11_stack=stack64#1 +# asm 2: movq r11_stack=352(%rsp) +movq %r11,352(%rsp) + +# qhasm: r12_stack = r12_caller +# asm 1: movq r12_stack=stack64#2 +# asm 2: movq r12_stack=360(%rsp) +movq %r12,360(%rsp) + +# qhasm: r13_stack = r13_caller +# asm 1: movq r13_stack=stack64#3 +# asm 2: movq r13_stack=368(%rsp) +movq %r13,368(%rsp) + +# qhasm: r14_stack = r14_caller +# asm 1: movq r14_stack=stack64#4 +# asm 2: movq r14_stack=376(%rsp) +movq %r14,376(%rsp) + +# qhasm: r15_stack = r15_caller +# asm 1: movq r15_stack=stack64#5 +# asm 2: movq r15_stack=384(%rsp) +movq %r15,384(%rsp) + +# qhasm: rbx_stack = rbx_caller +# asm 1: movq rbx_stack=stack64#6 +# asm 2: movq rbx_stack=392(%rsp) +movq %rbx,392(%rsp) + +# qhasm: rbp_stack = rbp_caller +# asm 1: movq rbp_stack=stack64#7 +# asm 2: movq rbp_stack=400(%rsp) +movq %rbp,400(%rsp) + +# qhasm: bytes = arg2 +# asm 1: mov bytes=int64#6 +# asm 2: mov bytes=%r9 +mov %rsi,%r9 + +# qhasm: out = arg1 +# asm 1: mov out=int64#1 +# asm 2: mov out=%rdi +mov %rdi,%rdi + +# qhasm: m = out +# asm 1: mov m=int64#2 +# asm 2: mov m=%rsi +mov %rdi,%rsi + +# qhasm: iv = arg3 +# asm 1: mov iv=int64#3 +# asm 2: mov iv=%rdx +mov %rdx,%rdx + +# qhasm: k = arg4 +# asm 1: mov k=int64#8 +# asm 2: mov k=%r10 +mov %rcx,%r10 + +# qhasm: unsigned>? bytes - 0 +# asm 1: cmp $0, +jbe ._done + +# qhasm: a = 0 +# asm 1: mov $0,>a=int64#7 +# asm 2: mov $0,>a=%rax +mov $0,%rax + +# qhasm: i = bytes +# asm 1: mov i=int64#4 +# asm 2: mov i=%rcx +mov %r9,%rcx + +# qhasm: while (i) { *out++ = a; --i } +rep stosb + +# qhasm: out -= bytes +# asm 1: sub r11_stack=stack64#1 +# asm 2: movq r11_stack=352(%rsp) +movq %r11,352(%rsp) + +# qhasm: r12_stack = r12_caller +# asm 1: movq r12_stack=stack64#2 +# asm 2: movq r12_stack=360(%rsp) +movq %r12,360(%rsp) + +# qhasm: r13_stack = r13_caller +# asm 1: movq r13_stack=stack64#3 +# asm 2: movq r13_stack=368(%rsp) +movq %r13,368(%rsp) + +# qhasm: r14_stack = r14_caller +# asm 1: movq r14_stack=stack64#4 +# asm 2: movq r14_stack=376(%rsp) +movq %r14,376(%rsp) + +# qhasm: r15_stack = r15_caller +# asm 1: movq r15_stack=stack64#5 +# asm 2: movq r15_stack=384(%rsp) +movq %r15,384(%rsp) + +# qhasm: rbx_stack = rbx_caller +# asm 1: movq rbx_stack=stack64#6 +# asm 2: movq rbx_stack=392(%rsp) +movq %rbx,392(%rsp) + +# qhasm: rbp_stack = rbp_caller +# asm 1: movq rbp_stack=stack64#7 +# asm 2: movq rbp_stack=400(%rsp) +movq %rbp,400(%rsp) + +# qhasm: out = arg1 +# asm 1: mov out=int64#1 +# asm 2: mov out=%rdi +mov %rdi,%rdi + +# qhasm: m = arg2 +# asm 1: mov m=int64#2 +# asm 2: mov m=%rsi +mov %rsi,%rsi + +# qhasm: bytes = arg3 +# asm 1: mov bytes=int64#6 +# asm 2: mov bytes=%r9 +mov %rdx,%r9 + +# qhasm: iv = arg4 +# asm 1: mov iv=int64#3 +# asm 2: mov iv=%rdx +mov %rcx,%rdx + +# qhasm: k = arg5 +# asm 1: mov k=int64#8 +# asm 2: mov k=%r10 +mov %r8,%r10 + +# qhasm: unsigned>? bytes - 0 +# asm 1: cmp $0, +jbe ._done +# comment:fp stack unchanged by fallthrough + +# qhasm: start: +._start: + +# qhasm: in12 = *(uint32 *) (k + 20) +# asm 1: movl 20(in12=int64#4d +# asm 2: movl 20(in12=%ecx +movl 20(%r10),%ecx + +# qhasm: in1 = *(uint32 *) (k + 0) +# asm 1: movl 0(in1=int64#5d +# asm 2: movl 0(in1=%r8d +movl 0(%r10),%r8d + +# qhasm: in6 = *(uint32 *) (iv + 0) +# asm 1: movl 0(in6=int64#7d +# asm 2: movl 0(in6=%eax +movl 0(%rdx),%eax + +# qhasm: in11 = *(uint32 *) (k + 16) +# asm 1: movl 16(in11=int64#9d +# asm 2: movl 16(in11=%r11d +movl 16(%r10),%r11d + +# qhasm: ((uint32 *)&x1)[0] = in12 +# asm 1: movl x1=stack128#1 +# asm 2: movl x1=0(%rsp) +movl %ecx,0(%rsp) + +# qhasm: ((uint32 *)&x1)[1] = in1 +# asm 1: movl in8=int64#4 +# asm 2: mov $0,>in8=%rcx +mov $0,%rcx + +# qhasm: in13 = *(uint32 *) (k + 24) +# asm 1: movl 24(in13=int64#5d +# asm 2: movl 24(in13=%r8d +movl 24(%r10),%r8d + +# qhasm: in2 = *(uint32 *) (k + 4) +# asm 1: movl 4(in2=int64#7d +# asm 2: movl 4(in2=%eax +movl 4(%r10),%eax + +# qhasm: in7 = *(uint32 *) (iv + 4) +# asm 1: movl 4(in7=int64#3d +# asm 2: movl 4(in7=%edx +movl 4(%rdx),%edx + +# qhasm: ((uint32 *)&x2)[0] = in8 +# asm 1: movl x2=stack128#2 +# asm 2: movl x2=16(%rsp) +movl %ecx,16(%rsp) + +# qhasm: ((uint32 *)&x2)[1] = in13 +# asm 1: movl in4=int64#3d +# asm 2: movl 12(in4=%edx +movl 12(%r10),%edx + +# qhasm: in9 = 0 +# asm 1: mov $0,>in9=int64#4 +# asm 2: mov $0,>in9=%rcx +mov $0,%rcx + +# qhasm: in14 = *(uint32 *) (k + 28) +# asm 1: movl 28(in14=int64#5d +# asm 2: movl 28(in14=%r8d +movl 28(%r10),%r8d + +# qhasm: in3 = *(uint32 *) (k + 8) +# asm 1: movl 8(in3=int64#7d +# asm 2: movl 8(in3=%eax +movl 8(%r10),%eax + +# qhasm: ((uint32 *)&x3)[0] = in4 +# asm 1: movl x3=stack128#3 +# asm 2: movl x3=32(%rsp) +movl %edx,32(%rsp) + +# qhasm: ((uint32 *)&x3)[1] = in9 +# asm 1: movl in0=int64#3 +# asm 2: mov $1634760805,>in0=%rdx +mov $1634760805,%rdx + +# qhasm: in5 = 857760878 +# asm 1: mov $857760878,>in5=int64#4 +# asm 2: mov $857760878,>in5=%rcx +mov $857760878,%rcx + +# qhasm: in10 = 2036477234 +# asm 1: mov $2036477234,>in10=int64#5 +# asm 2: mov $2036477234,>in10=%r8 +mov $2036477234,%r8 + +# qhasm: in15 = 1797285236 +# asm 1: mov $1797285236,>in15=int64#7 +# asm 2: mov $1797285236,>in15=%rax +mov $1797285236,%rax + +# qhasm: ((uint32 *)&x0)[0] = in0 +# asm 1: movl x0=stack128#4 +# asm 2: movl x0=48(%rsp) +movl %edx,48(%rsp) + +# qhasm: ((uint32 *)&x0)[1] = in5 +# asm 1: movl z0=int6464#1 +# asm 2: movdqa z0=%xmm0 +movdqa 48(%rsp),%xmm0 + +# qhasm: z5 = z0[1,1,1,1] +# asm 1: pshufd $0x55,z5=int6464#2 +# asm 2: pshufd $0x55,z5=%xmm1 +pshufd $0x55,%xmm0,%xmm1 + +# qhasm: z10 = z0[2,2,2,2] +# asm 1: pshufd $0xaa,z10=int6464#3 +# asm 2: pshufd $0xaa,z10=%xmm2 +pshufd $0xaa,%xmm0,%xmm2 + +# qhasm: z15 = z0[3,3,3,3] +# asm 1: pshufd $0xff,z15=int6464#4 +# asm 2: pshufd $0xff,z15=%xmm3 +pshufd $0xff,%xmm0,%xmm3 + +# qhasm: z0 = z0[0,0,0,0] +# asm 1: pshufd $0x00,z0=int6464#1 +# asm 2: pshufd $0x00,z0=%xmm0 +pshufd $0x00,%xmm0,%xmm0 + +# qhasm: orig5 = z5 +# asm 1: movdqa orig5=stack128#5 +# asm 2: movdqa orig5=64(%rsp) +movdqa %xmm1,64(%rsp) + +# qhasm: orig10 = z10 +# asm 1: movdqa orig10=stack128#6 +# asm 2: movdqa orig10=80(%rsp) +movdqa %xmm2,80(%rsp) + +# qhasm: orig15 = z15 +# asm 1: movdqa orig15=stack128#7 +# asm 2: movdqa orig15=96(%rsp) +movdqa %xmm3,96(%rsp) + +# qhasm: orig0 = z0 +# asm 1: movdqa orig0=stack128#8 +# asm 2: movdqa orig0=112(%rsp) +movdqa %xmm0,112(%rsp) + +# qhasm: z1 = x1 +# asm 1: movdqa z1=int6464#1 +# asm 2: movdqa z1=%xmm0 +movdqa 0(%rsp),%xmm0 + +# qhasm: z6 = z1[2,2,2,2] +# asm 1: pshufd $0xaa,z6=int6464#2 +# asm 2: pshufd $0xaa,z6=%xmm1 +pshufd $0xaa,%xmm0,%xmm1 + +# qhasm: z11 = z1[3,3,3,3] +# asm 1: pshufd $0xff,z11=int6464#3 +# asm 2: pshufd $0xff,z11=%xmm2 +pshufd $0xff,%xmm0,%xmm2 + +# qhasm: z12 = z1[0,0,0,0] +# asm 1: pshufd $0x00,z12=int6464#4 +# asm 2: pshufd $0x00,z12=%xmm3 +pshufd $0x00,%xmm0,%xmm3 + +# qhasm: z1 = z1[1,1,1,1] +# asm 1: pshufd $0x55,z1=int6464#1 +# asm 2: pshufd $0x55,z1=%xmm0 +pshufd $0x55,%xmm0,%xmm0 + +# qhasm: orig6 = z6 +# asm 1: movdqa orig6=stack128#9 +# asm 2: movdqa orig6=128(%rsp) +movdqa %xmm1,128(%rsp) + +# qhasm: orig11 = z11 +# asm 1: movdqa orig11=stack128#10 +# asm 2: movdqa orig11=144(%rsp) +movdqa %xmm2,144(%rsp) + +# qhasm: orig12 = z12 +# asm 1: movdqa orig12=stack128#11 +# asm 2: movdqa orig12=160(%rsp) +movdqa %xmm3,160(%rsp) + +# qhasm: orig1 = z1 +# asm 1: movdqa orig1=stack128#12 +# asm 2: movdqa orig1=176(%rsp) +movdqa %xmm0,176(%rsp) + +# qhasm: z2 = x2 +# asm 1: movdqa z2=int6464#1 +# asm 2: movdqa z2=%xmm0 +movdqa 16(%rsp),%xmm0 + +# qhasm: z7 = z2[3,3,3,3] +# asm 1: pshufd $0xff,z7=int6464#2 +# asm 2: pshufd $0xff,z7=%xmm1 +pshufd $0xff,%xmm0,%xmm1 + +# qhasm: z13 = z2[1,1,1,1] +# asm 1: pshufd $0x55,z13=int6464#3 +# asm 2: pshufd $0x55,z13=%xmm2 +pshufd $0x55,%xmm0,%xmm2 + +# qhasm: z2 = z2[2,2,2,2] +# asm 1: pshufd $0xaa,z2=int6464#1 +# asm 2: pshufd $0xaa,z2=%xmm0 +pshufd $0xaa,%xmm0,%xmm0 + +# qhasm: orig7 = z7 +# asm 1: movdqa orig7=stack128#13 +# asm 2: movdqa orig7=192(%rsp) +movdqa %xmm1,192(%rsp) + +# qhasm: orig13 = z13 +# asm 1: movdqa orig13=stack128#14 +# asm 2: movdqa orig13=208(%rsp) +movdqa %xmm2,208(%rsp) + +# qhasm: orig2 = z2 +# asm 1: movdqa orig2=stack128#15 +# asm 2: movdqa orig2=224(%rsp) +movdqa %xmm0,224(%rsp) + +# qhasm: z3 = x3 +# asm 1: movdqa z3=int6464#1 +# asm 2: movdqa z3=%xmm0 +movdqa 32(%rsp),%xmm0 + +# qhasm: z4 = z3[0,0,0,0] +# asm 1: pshufd $0x00,z4=int6464#2 +# asm 2: pshufd $0x00,z4=%xmm1 +pshufd $0x00,%xmm0,%xmm1 + +# qhasm: z14 = z3[2,2,2,2] +# asm 1: pshufd $0xaa,z14=int6464#3 +# asm 2: pshufd $0xaa,z14=%xmm2 +pshufd $0xaa,%xmm0,%xmm2 + +# qhasm: z3 = z3[3,3,3,3] +# asm 1: pshufd $0xff,z3=int6464#1 +# asm 2: pshufd $0xff,z3=%xmm0 +pshufd $0xff,%xmm0,%xmm0 + +# qhasm: orig4 = z4 +# asm 1: movdqa orig4=stack128#16 +# asm 2: movdqa orig4=240(%rsp) +movdqa %xmm1,240(%rsp) + +# qhasm: orig14 = z14 +# asm 1: movdqa orig14=stack128#17 +# asm 2: movdqa orig14=256(%rsp) +movdqa %xmm2,256(%rsp) + +# qhasm: orig3 = z3 +# asm 1: movdqa orig3=stack128#18 +# asm 2: movdqa orig3=272(%rsp) +movdqa %xmm0,272(%rsp) + +# qhasm: bytesatleast256: +._bytesatleast256: + +# qhasm: in8 = ((uint32 *)&x2)[0] +# asm 1: movl in8=int64#3d +# asm 2: movl in8=%edx +movl 16(%rsp),%edx + +# qhasm: in9 = ((uint32 *)&x3)[1] +# asm 1: movl 4+in9=int64#4d +# asm 2: movl 4+in9=%ecx +movl 4+32(%rsp),%ecx + +# qhasm: ((uint32 *) &orig8)[0] = in8 +# asm 1: movl orig8=stack128#19 +# asm 2: movl orig8=288(%rsp) +movl %edx,288(%rsp) + +# qhasm: ((uint32 *) &orig9)[0] = in9 +# asm 1: movl orig9=stack128#20 +# asm 2: movl orig9=304(%rsp) +movl %ecx,304(%rsp) + +# qhasm: in8 += 1 +# asm 1: add $1,in9=int64#4 +# asm 2: mov in9=%rcx +mov %rdx,%rcx + +# qhasm: (uint64) in9 >>= 32 +# asm 1: shr $32,in9=int64#4 +# asm 2: mov in9=%rcx +mov %rdx,%rcx + +# qhasm: (uint64) in9 >>= 32 +# asm 1: shr $32,in9=int64#4 +# asm 2: mov in9=%rcx +mov %rdx,%rcx + +# qhasm: (uint64) in9 >>= 32 +# asm 1: shr $32,in9=int64#4 +# asm 2: mov in9=%rcx +mov %rdx,%rcx + +# qhasm: (uint64) in9 >>= 32 +# asm 1: shr $32,x2=stack128#2 +# asm 2: movl x2=16(%rsp) +movl %edx,16(%rsp) + +# qhasm: ((uint32 *)&x3)[1] = in9 +# asm 1: movl bytes_backup=stack64#8 +# asm 2: movq bytes_backup=408(%rsp) +movq %r9,408(%rsp) + +# qhasm: i = 12 +# asm 1: mov $12,>i=int64#3 +# asm 2: mov $12,>i=%rdx +mov $12,%rdx + +# qhasm: z5 = orig5 +# asm 1: movdqa z5=int6464#1 +# asm 2: movdqa z5=%xmm0 +movdqa 64(%rsp),%xmm0 + +# qhasm: z10 = orig10 +# asm 1: movdqa z10=int6464#2 +# asm 2: movdqa z10=%xmm1 +movdqa 80(%rsp),%xmm1 + +# qhasm: z15 = orig15 +# asm 1: movdqa z15=int6464#3 +# asm 2: movdqa z15=%xmm2 +movdqa 96(%rsp),%xmm2 + +# qhasm: z14 = orig14 +# asm 1: movdqa z14=int6464#4 +# asm 2: movdqa z14=%xmm3 +movdqa 256(%rsp),%xmm3 + +# qhasm: z3 = orig3 +# asm 1: movdqa z3=int6464#5 +# asm 2: movdqa z3=%xmm4 +movdqa 272(%rsp),%xmm4 + +# qhasm: z6 = orig6 +# asm 1: movdqa z6=int6464#6 +# asm 2: movdqa z6=%xmm5 +movdqa 128(%rsp),%xmm5 + +# qhasm: z11 = orig11 +# asm 1: movdqa z11=int6464#7 +# asm 2: movdqa z11=%xmm6 +movdqa 144(%rsp),%xmm6 + +# qhasm: z1 = orig1 +# asm 1: movdqa z1=int6464#8 +# asm 2: movdqa z1=%xmm7 +movdqa 176(%rsp),%xmm7 + +# qhasm: z7 = orig7 +# asm 1: movdqa z7=int6464#9 +# asm 2: movdqa z7=%xmm8 +movdqa 192(%rsp),%xmm8 + +# qhasm: z13 = orig13 +# asm 1: movdqa z13=int6464#10 +# asm 2: movdqa z13=%xmm9 +movdqa 208(%rsp),%xmm9 + +# qhasm: z2 = orig2 +# asm 1: movdqa z2=int6464#11 +# asm 2: movdqa z2=%xmm10 +movdqa 224(%rsp),%xmm10 + +# qhasm: z9 = orig9 +# asm 1: movdqa z9=int6464#12 +# asm 2: movdqa z9=%xmm11 +movdqa 304(%rsp),%xmm11 + +# qhasm: z0 = orig0 +# asm 1: movdqa z0=int6464#13 +# asm 2: movdqa z0=%xmm12 +movdqa 112(%rsp),%xmm12 + +# qhasm: z12 = orig12 +# asm 1: movdqa z12=int6464#14 +# asm 2: movdqa z12=%xmm13 +movdqa 160(%rsp),%xmm13 + +# qhasm: z4 = orig4 +# asm 1: movdqa z4=int6464#15 +# asm 2: movdqa z4=%xmm14 +movdqa 240(%rsp),%xmm14 + +# qhasm: z8 = orig8 +# asm 1: movdqa z8=int6464#16 +# asm 2: movdqa z8=%xmm15 +movdqa 288(%rsp),%xmm15 + +# qhasm: mainloop1: +._mainloop1: + +# qhasm: z10_stack = z10 +# asm 1: movdqa z10_stack=stack128#21 +# asm 2: movdqa z10_stack=320(%rsp) +movdqa %xmm1,320(%rsp) + +# qhasm: z15_stack = z15 +# asm 1: movdqa z15_stack=stack128#22 +# asm 2: movdqa z15_stack=336(%rsp) +movdqa %xmm2,336(%rsp) + +# qhasm: y4 = z12 +# asm 1: movdqa y4=int6464#2 +# asm 2: movdqa y4=%xmm1 +movdqa %xmm13,%xmm1 + +# qhasm: uint32323232 y4 += z0 +# asm 1: paddd r4=int6464#3 +# asm 2: movdqa r4=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y4 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,y9=int6464#2 +# asm 2: movdqa y9=%xmm1 +movdqa %xmm7,%xmm1 + +# qhasm: uint32323232 y9 += z5 +# asm 1: paddd r9=int6464#3 +# asm 2: movdqa r9=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y9 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,y8=int6464#2 +# asm 2: movdqa y8=%xmm1 +movdqa %xmm12,%xmm1 + +# qhasm: uint32323232 y8 += z4 +# asm 1: paddd r8=int6464#3 +# asm 2: movdqa r8=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y8 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y13=int6464#2 +# asm 2: movdqa y13=%xmm1 +movdqa %xmm0,%xmm1 + +# qhasm: uint32323232 y13 += z9 +# asm 1: paddd r13=int6464#3 +# asm 2: movdqa r13=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y13 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y12=int6464#2 +# asm 2: movdqa y12=%xmm1 +movdqa %xmm14,%xmm1 + +# qhasm: uint32323232 y12 += z8 +# asm 1: paddd r12=int6464#3 +# asm 2: movdqa r12=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y12 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y1=int6464#2 +# asm 2: movdqa y1=%xmm1 +movdqa %xmm11,%xmm1 + +# qhasm: uint32323232 y1 += z13 +# asm 1: paddd r1=int6464#3 +# asm 2: movdqa r1=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y1 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y0=int6464#2 +# asm 2: movdqa y0=%xmm1 +movdqa %xmm15,%xmm1 + +# qhasm: uint32323232 y0 += z12 +# asm 1: paddd r0=int6464#3 +# asm 2: movdqa r0=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y0 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,z10=int6464#2 +# asm 2: movdqa z10=%xmm1 +movdqa 320(%rsp),%xmm1 + +# qhasm: z0_stack = z0 +# asm 1: movdqa z0_stack=stack128#21 +# asm 2: movdqa z0_stack=320(%rsp) +movdqa %xmm12,320(%rsp) + +# qhasm: y5 = z13 +# asm 1: movdqa y5=int6464#3 +# asm 2: movdqa y5=%xmm2 +movdqa %xmm9,%xmm2 + +# qhasm: uint32323232 y5 += z1 +# asm 1: paddd r5=int6464#13 +# asm 2: movdqa r5=%xmm12 +movdqa %xmm2,%xmm12 + +# qhasm: uint32323232 y5 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,y14=int6464#3 +# asm 2: movdqa y14=%xmm2 +movdqa %xmm5,%xmm2 + +# qhasm: uint32323232 y14 += z10 +# asm 1: paddd r14=int6464#13 +# asm 2: movdqa r14=%xmm12 +movdqa %xmm2,%xmm12 + +# qhasm: uint32323232 y14 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,z15=int6464#3 +# asm 2: movdqa z15=%xmm2 +movdqa 336(%rsp),%xmm2 + +# qhasm: z5_stack = z5 +# asm 1: movdqa z5_stack=stack128#22 +# asm 2: movdqa z5_stack=336(%rsp) +movdqa %xmm0,336(%rsp) + +# qhasm: y3 = z11 +# asm 1: movdqa y3=int6464#1 +# asm 2: movdqa y3=%xmm0 +movdqa %xmm6,%xmm0 + +# qhasm: uint32323232 y3 += z15 +# asm 1: paddd r3=int6464#13 +# asm 2: movdqa r3=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y3 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,y2=int6464#1 +# asm 2: movdqa y2=%xmm0 +movdqa %xmm1,%xmm0 + +# qhasm: uint32323232 y2 += z14 +# asm 1: paddd r2=int6464#13 +# asm 2: movdqa r2=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y2 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y7=int6464#1 +# asm 2: movdqa y7=%xmm0 +movdqa %xmm2,%xmm0 + +# qhasm: uint32323232 y7 += z3 +# asm 1: paddd r7=int6464#13 +# asm 2: movdqa r7=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y7 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y6=int6464#1 +# asm 2: movdqa y6=%xmm0 +movdqa %xmm3,%xmm0 + +# qhasm: uint32323232 y6 += z2 +# asm 1: paddd r6=int6464#13 +# asm 2: movdqa r6=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y6 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y11=int6464#1 +# asm 2: movdqa y11=%xmm0 +movdqa %xmm4,%xmm0 + +# qhasm: uint32323232 y11 += z7 +# asm 1: paddd r11=int6464#13 +# asm 2: movdqa r11=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y11 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y10=int6464#1 +# asm 2: movdqa y10=%xmm0 +movdqa %xmm10,%xmm0 + +# qhasm: uint32323232 y10 += z6 +# asm 1: paddd r10=int6464#13 +# asm 2: movdqa r10=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y10 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,z0=int6464#1 +# asm 2: movdqa z0=%xmm0 +movdqa 320(%rsp),%xmm0 + +# qhasm: z10_stack = z10 +# asm 1: movdqa z10_stack=stack128#21 +# asm 2: movdqa z10_stack=320(%rsp) +movdqa %xmm1,320(%rsp) + +# qhasm: y1 = z3 +# asm 1: movdqa y1=int6464#2 +# asm 2: movdqa y1=%xmm1 +movdqa %xmm4,%xmm1 + +# qhasm: uint32323232 y1 += z0 +# asm 1: paddd r1=int6464#13 +# asm 2: movdqa r1=%xmm12 +movdqa %xmm1,%xmm12 + +# qhasm: uint32323232 y1 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,y15=int6464#2 +# asm 2: movdqa y15=%xmm1 +movdqa %xmm8,%xmm1 + +# qhasm: uint32323232 y15 += z11 +# asm 1: paddd r15=int6464#13 +# asm 2: movdqa r15=%xmm12 +movdqa %xmm1,%xmm12 + +# qhasm: uint32323232 y15 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,z5=int6464#13 +# asm 2: movdqa z5=%xmm12 +movdqa 336(%rsp),%xmm12 + +# qhasm: z15_stack = z15 +# asm 1: movdqa z15_stack=stack128#22 +# asm 2: movdqa z15_stack=336(%rsp) +movdqa %xmm2,336(%rsp) + +# qhasm: y6 = z4 +# asm 1: movdqa y6=int6464#2 +# asm 2: movdqa y6=%xmm1 +movdqa %xmm14,%xmm1 + +# qhasm: uint32323232 y6 += z5 +# asm 1: paddd r6=int6464#3 +# asm 2: movdqa r6=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y6 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,y2=int6464#2 +# asm 2: movdqa y2=%xmm1 +movdqa %xmm0,%xmm1 + +# qhasm: uint32323232 y2 += z1 +# asm 1: paddd r2=int6464#3 +# asm 2: movdqa r2=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y2 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y7=int6464#2 +# asm 2: movdqa y7=%xmm1 +movdqa %xmm12,%xmm1 + +# qhasm: uint32323232 y7 += z6 +# asm 1: paddd r7=int6464#3 +# asm 2: movdqa r7=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y7 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y3=int6464#2 +# asm 2: movdqa y3=%xmm1 +movdqa %xmm7,%xmm1 + +# qhasm: uint32323232 y3 += z2 +# asm 1: paddd r3=int6464#3 +# asm 2: movdqa r3=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y3 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y4=int6464#2 +# asm 2: movdqa y4=%xmm1 +movdqa %xmm5,%xmm1 + +# qhasm: uint32323232 y4 += z7 +# asm 1: paddd r4=int6464#3 +# asm 2: movdqa r4=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y4 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y0=int6464#2 +# asm 2: movdqa y0=%xmm1 +movdqa %xmm10,%xmm1 + +# qhasm: uint32323232 y0 += z3 +# asm 1: paddd r0=int6464#3 +# asm 2: movdqa r0=%xmm2 +movdqa %xmm1,%xmm2 + +# qhasm: uint32323232 y0 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,z10=int6464#2 +# asm 2: movdqa z10=%xmm1 +movdqa 320(%rsp),%xmm1 + +# qhasm: z0_stack = z0 +# asm 1: movdqa z0_stack=stack128#21 +# asm 2: movdqa z0_stack=320(%rsp) +movdqa %xmm0,320(%rsp) + +# qhasm: y5 = z7 +# asm 1: movdqa y5=int6464#1 +# asm 2: movdqa y5=%xmm0 +movdqa %xmm8,%xmm0 + +# qhasm: uint32323232 y5 += z4 +# asm 1: paddd r5=int6464#3 +# asm 2: movdqa r5=%xmm2 +movdqa %xmm0,%xmm2 + +# qhasm: uint32323232 y5 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,y11=int6464#1 +# asm 2: movdqa y11=%xmm0 +movdqa %xmm11,%xmm0 + +# qhasm: uint32323232 y11 += z10 +# asm 1: paddd r11=int6464#3 +# asm 2: movdqa r11=%xmm2 +movdqa %xmm0,%xmm2 + +# qhasm: uint32323232 y11 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,z15=int6464#3 +# asm 2: movdqa z15=%xmm2 +movdqa 336(%rsp),%xmm2 + +# qhasm: z5_stack = z5 +# asm 1: movdqa z5_stack=stack128#22 +# asm 2: movdqa z5_stack=336(%rsp) +movdqa %xmm12,336(%rsp) + +# qhasm: y12 = z14 +# asm 1: movdqa y12=int6464#1 +# asm 2: movdqa y12=%xmm0 +movdqa %xmm3,%xmm0 + +# qhasm: uint32323232 y12 += z15 +# asm 1: paddd r12=int6464#13 +# asm 2: movdqa r12=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y12 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,y8=int6464#1 +# asm 2: movdqa y8=%xmm0 +movdqa %xmm1,%xmm0 + +# qhasm: uint32323232 y8 += z11 +# asm 1: paddd r8=int6464#13 +# asm 2: movdqa r8=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y8 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y13=int6464#1 +# asm 2: movdqa y13=%xmm0 +movdqa %xmm2,%xmm0 + +# qhasm: uint32323232 y13 += z12 +# asm 1: paddd r13=int6464#13 +# asm 2: movdqa r13=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y13 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,y9=int6464#1 +# asm 2: movdqa y9=%xmm0 +movdqa %xmm6,%xmm0 + +# qhasm: uint32323232 y9 += z8 +# asm 1: paddd r9=int6464#13 +# asm 2: movdqa r9=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y9 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y14=int6464#1 +# asm 2: movdqa y14=%xmm0 +movdqa %xmm13,%xmm0 + +# qhasm: uint32323232 y14 += z13 +# asm 1: paddd r14=int6464#13 +# asm 2: movdqa r14=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y14 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,y10=int6464#1 +# asm 2: movdqa y10=%xmm0 +movdqa %xmm15,%xmm0 + +# qhasm: uint32323232 y10 += z9 +# asm 1: paddd r10=int6464#13 +# asm 2: movdqa r10=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y10 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,y15=int6464#1 +# asm 2: movdqa y15=%xmm0 +movdqa %xmm9,%xmm0 + +# qhasm: uint32323232 y15 += z14 +# asm 1: paddd r15=int6464#13 +# asm 2: movdqa r15=%xmm12 +movdqa %xmm0,%xmm12 + +# qhasm: uint32323232 y15 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,z0=int6464#13 +# asm 2: movdqa z0=%xmm12 +movdqa 320(%rsp),%xmm12 + +# qhasm: z5 = z5_stack +# asm 1: movdqa z5=int6464#1 +# asm 2: movdqa z5=%xmm0 +movdqa 336(%rsp),%xmm0 + +# qhasm: unsigned>? i -= 2 +# asm 1: sub $2, +ja ._mainloop1 + +# qhasm: uint32323232 z0 += orig0 +# asm 1: paddd in0=int64#3 +# asm 2: movd in0=%rdx +movd %xmm12,%rdx + +# qhasm: in1 = z1 +# asm 1: movd in1=int64#4 +# asm 2: movd in1=%rcx +movd %xmm7,%rcx + +# qhasm: in2 = z2 +# asm 1: movd in2=int64#5 +# asm 2: movd in2=%r8 +movd %xmm10,%r8 + +# qhasm: in3 = z3 +# asm 1: movd in3=int64#6 +# asm 2: movd in3=%r9 +movd %xmm4,%r9 + +# qhasm: z0 <<<= 96 +# asm 1: pshufd $0x39,in0=int64#3 +# asm 2: movd in0=%rdx +movd %xmm12,%rdx + +# qhasm: in1 = z1 +# asm 1: movd in1=int64#4 +# asm 2: movd in1=%rcx +movd %xmm7,%rcx + +# qhasm: in2 = z2 +# asm 1: movd in2=int64#5 +# asm 2: movd in2=%r8 +movd %xmm10,%r8 + +# qhasm: in3 = z3 +# asm 1: movd in3=int64#6 +# asm 2: movd in3=%r9 +movd %xmm4,%r9 + +# qhasm: z0 <<<= 96 +# asm 1: pshufd $0x39,in0=int64#3 +# asm 2: movd in0=%rdx +movd %xmm12,%rdx + +# qhasm: in1 = z1 +# asm 1: movd in1=int64#4 +# asm 2: movd in1=%rcx +movd %xmm7,%rcx + +# qhasm: in2 = z2 +# asm 1: movd in2=int64#5 +# asm 2: movd in2=%r8 +movd %xmm10,%r8 + +# qhasm: in3 = z3 +# asm 1: movd in3=int64#6 +# asm 2: movd in3=%r9 +movd %xmm4,%r9 + +# qhasm: z0 <<<= 96 +# asm 1: pshufd $0x39,in0=int64#3 +# asm 2: movd in0=%rdx +movd %xmm12,%rdx + +# qhasm: in1 = z1 +# asm 1: movd in1=int64#4 +# asm 2: movd in1=%rcx +movd %xmm7,%rcx + +# qhasm: in2 = z2 +# asm 1: movd in2=int64#5 +# asm 2: movd in2=%r8 +movd %xmm10,%r8 + +# qhasm: in3 = z3 +# asm 1: movd in3=int64#6 +# asm 2: movd in3=%r9 +movd %xmm4,%r9 + +# qhasm: (uint32) in0 ^= *(uint32 *) (m + 192) +# asm 1: xorl 192(in4=int64#3 +# asm 2: movd in4=%rdx +movd %xmm14,%rdx + +# qhasm: in5 = z5 +# asm 1: movd in5=int64#4 +# asm 2: movd in5=%rcx +movd %xmm0,%rcx + +# qhasm: in6 = z6 +# asm 1: movd in6=int64#5 +# asm 2: movd in6=%r8 +movd %xmm5,%r8 + +# qhasm: in7 = z7 +# asm 1: movd in7=int64#6 +# asm 2: movd in7=%r9 +movd %xmm8,%r9 + +# qhasm: z4 <<<= 96 +# asm 1: pshufd $0x39,in4=int64#3 +# asm 2: movd in4=%rdx +movd %xmm14,%rdx + +# qhasm: in5 = z5 +# asm 1: movd in5=int64#4 +# asm 2: movd in5=%rcx +movd %xmm0,%rcx + +# qhasm: in6 = z6 +# asm 1: movd in6=int64#5 +# asm 2: movd in6=%r8 +movd %xmm5,%r8 + +# qhasm: in7 = z7 +# asm 1: movd in7=int64#6 +# asm 2: movd in7=%r9 +movd %xmm8,%r9 + +# qhasm: z4 <<<= 96 +# asm 1: pshufd $0x39,in4=int64#3 +# asm 2: movd in4=%rdx +movd %xmm14,%rdx + +# qhasm: in5 = z5 +# asm 1: movd in5=int64#4 +# asm 2: movd in5=%rcx +movd %xmm0,%rcx + +# qhasm: in6 = z6 +# asm 1: movd in6=int64#5 +# asm 2: movd in6=%r8 +movd %xmm5,%r8 + +# qhasm: in7 = z7 +# asm 1: movd in7=int64#6 +# asm 2: movd in7=%r9 +movd %xmm8,%r9 + +# qhasm: z4 <<<= 96 +# asm 1: pshufd $0x39,in4=int64#3 +# asm 2: movd in4=%rdx +movd %xmm14,%rdx + +# qhasm: in5 = z5 +# asm 1: movd in5=int64#4 +# asm 2: movd in5=%rcx +movd %xmm0,%rcx + +# qhasm: in6 = z6 +# asm 1: movd in6=int64#5 +# asm 2: movd in6=%r8 +movd %xmm5,%r8 + +# qhasm: in7 = z7 +# asm 1: movd in7=int64#6 +# asm 2: movd in7=%r9 +movd %xmm8,%r9 + +# qhasm: (uint32) in4 ^= *(uint32 *) (m + 208) +# asm 1: xorl 208(in8=int64#3 +# asm 2: movd in8=%rdx +movd %xmm15,%rdx + +# qhasm: in9 = z9 +# asm 1: movd in9=int64#4 +# asm 2: movd in9=%rcx +movd %xmm11,%rcx + +# qhasm: in10 = z10 +# asm 1: movd in10=int64#5 +# asm 2: movd in10=%r8 +movd %xmm1,%r8 + +# qhasm: in11 = z11 +# asm 1: movd in11=int64#6 +# asm 2: movd in11=%r9 +movd %xmm6,%r9 + +# qhasm: z8 <<<= 96 +# asm 1: pshufd $0x39,in8=int64#3 +# asm 2: movd in8=%rdx +movd %xmm15,%rdx + +# qhasm: in9 = z9 +# asm 1: movd in9=int64#4 +# asm 2: movd in9=%rcx +movd %xmm11,%rcx + +# qhasm: in10 = z10 +# asm 1: movd in10=int64#5 +# asm 2: movd in10=%r8 +movd %xmm1,%r8 + +# qhasm: in11 = z11 +# asm 1: movd in11=int64#6 +# asm 2: movd in11=%r9 +movd %xmm6,%r9 + +# qhasm: z8 <<<= 96 +# asm 1: pshufd $0x39,in8=int64#3 +# asm 2: movd in8=%rdx +movd %xmm15,%rdx + +# qhasm: in9 = z9 +# asm 1: movd in9=int64#4 +# asm 2: movd in9=%rcx +movd %xmm11,%rcx + +# qhasm: in10 = z10 +# asm 1: movd in10=int64#5 +# asm 2: movd in10=%r8 +movd %xmm1,%r8 + +# qhasm: in11 = z11 +# asm 1: movd in11=int64#6 +# asm 2: movd in11=%r9 +movd %xmm6,%r9 + +# qhasm: z8 <<<= 96 +# asm 1: pshufd $0x39,in8=int64#3 +# asm 2: movd in8=%rdx +movd %xmm15,%rdx + +# qhasm: in9 = z9 +# asm 1: movd in9=int64#4 +# asm 2: movd in9=%rcx +movd %xmm11,%rcx + +# qhasm: in10 = z10 +# asm 1: movd in10=int64#5 +# asm 2: movd in10=%r8 +movd %xmm1,%r8 + +# qhasm: in11 = z11 +# asm 1: movd in11=int64#6 +# asm 2: movd in11=%r9 +movd %xmm6,%r9 + +# qhasm: (uint32) in8 ^= *(uint32 *) (m + 224) +# asm 1: xorl 224(in12=int64#3 +# asm 2: movd in12=%rdx +movd %xmm13,%rdx + +# qhasm: in13 = z13 +# asm 1: movd in13=int64#4 +# asm 2: movd in13=%rcx +movd %xmm9,%rcx + +# qhasm: in14 = z14 +# asm 1: movd in14=int64#5 +# asm 2: movd in14=%r8 +movd %xmm3,%r8 + +# qhasm: in15 = z15 +# asm 1: movd in15=int64#6 +# asm 2: movd in15=%r9 +movd %xmm2,%r9 + +# qhasm: z12 <<<= 96 +# asm 1: pshufd $0x39,in12=int64#3 +# asm 2: movd in12=%rdx +movd %xmm13,%rdx + +# qhasm: in13 = z13 +# asm 1: movd in13=int64#4 +# asm 2: movd in13=%rcx +movd %xmm9,%rcx + +# qhasm: in14 = z14 +# asm 1: movd in14=int64#5 +# asm 2: movd in14=%r8 +movd %xmm3,%r8 + +# qhasm: in15 = z15 +# asm 1: movd in15=int64#6 +# asm 2: movd in15=%r9 +movd %xmm2,%r9 + +# qhasm: z12 <<<= 96 +# asm 1: pshufd $0x39,in12=int64#3 +# asm 2: movd in12=%rdx +movd %xmm13,%rdx + +# qhasm: in13 = z13 +# asm 1: movd in13=int64#4 +# asm 2: movd in13=%rcx +movd %xmm9,%rcx + +# qhasm: in14 = z14 +# asm 1: movd in14=int64#5 +# asm 2: movd in14=%r8 +movd %xmm3,%r8 + +# qhasm: in15 = z15 +# asm 1: movd in15=int64#6 +# asm 2: movd in15=%r9 +movd %xmm2,%r9 + +# qhasm: z12 <<<= 96 +# asm 1: pshufd $0x39,in12=int64#3 +# asm 2: movd in12=%rdx +movd %xmm13,%rdx + +# qhasm: in13 = z13 +# asm 1: movd in13=int64#4 +# asm 2: movd in13=%rcx +movd %xmm9,%rcx + +# qhasm: in14 = z14 +# asm 1: movd in14=int64#5 +# asm 2: movd in14=%r8 +movd %xmm3,%r8 + +# qhasm: in15 = z15 +# asm 1: movd in15=int64#6 +# asm 2: movd in15=%r9 +movd %xmm2,%r9 + +# qhasm: (uint32) in12 ^= *(uint32 *) (m + 240) +# asm 1: xorl 240(bytes=int64#6 +# asm 2: movq bytes=%r9 +movq 408(%rsp),%r9 + +# qhasm: bytes -= 256 +# asm 1: sub $256,? bytes - 0 +# asm 1: cmp $0, +jbe ._done +# comment:fp stack unchanged by fallthrough + +# qhasm: bytesbetween1and255: +._bytesbetween1and255: + +# qhasm: unsignedctarget=int64#3 +# asm 2: mov ctarget=%rdx +mov %rdi,%rdx + +# qhasm: out = &tmp +# asm 1: leaq out=int64#1 +# asm 2: leaq out=%rdi +leaq 416(%rsp),%rdi + +# qhasm: i = bytes +# asm 1: mov i=int64#4 +# asm 2: mov i=%rcx +mov %r9,%rcx + +# qhasm: while (i) { *out++ = *m++; --i } +rep movsb + +# qhasm: out = &tmp +# asm 1: leaq out=int64#1 +# asm 2: leaq out=%rdi +leaq 416(%rsp),%rdi + +# qhasm: m = &tmp +# asm 1: leaq m=int64#2 +# asm 2: leaq m=%rsi +leaq 416(%rsp),%rsi +# comment:fp stack unchanged by fallthrough + +# qhasm: nocopy: +._nocopy: + +# qhasm: bytes_backup = bytes +# asm 1: movq bytes_backup=stack64#8 +# asm 2: movq bytes_backup=408(%rsp) +movq %r9,408(%rsp) + +# qhasm: diag0 = x0 +# asm 1: movdqa diag0=int6464#1 +# asm 2: movdqa diag0=%xmm0 +movdqa 48(%rsp),%xmm0 + +# qhasm: diag1 = x1 +# asm 1: movdqa diag1=int6464#2 +# asm 2: movdqa diag1=%xmm1 +movdqa 0(%rsp),%xmm1 + +# qhasm: diag2 = x2 +# asm 1: movdqa diag2=int6464#3 +# asm 2: movdqa diag2=%xmm2 +movdqa 16(%rsp),%xmm2 + +# qhasm: diag3 = x3 +# asm 1: movdqa diag3=int6464#4 +# asm 2: movdqa diag3=%xmm3 +movdqa 32(%rsp),%xmm3 + +# qhasm: a0 = diag1 +# asm 1: movdqa a0=int6464#5 +# asm 2: movdqa a0=%xmm4 +movdqa %xmm1,%xmm4 + +# qhasm: i = 12 +# asm 1: mov $12,>i=int64#4 +# asm 2: mov $12,>i=%rcx +mov $12,%rcx + +# qhasm: mainloop2: +._mainloop2: + +# qhasm: uint32323232 a0 += diag0 +# asm 1: paddd a1=int6464#6 +# asm 2: movdqa a1=%xmm5 +movdqa %xmm0,%xmm5 + +# qhasm: b0 = a0 +# asm 1: movdqa b0=int6464#7 +# asm 2: movdqa b0=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a0 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,a2=int6464#5 +# asm 2: movdqa a2=%xmm4 +movdqa %xmm3,%xmm4 + +# qhasm: b1 = a1 +# asm 1: movdqa b1=int6464#7 +# asm 2: movdqa b1=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a1 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,a3=int6464#6 +# asm 2: movdqa a3=%xmm5 +movdqa %xmm2,%xmm5 + +# qhasm: b2 = a2 +# asm 1: movdqa b2=int6464#7 +# asm 2: movdqa b2=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a2 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,a4=int6464#5 +# asm 2: movdqa a4=%xmm4 +movdqa %xmm3,%xmm4 + +# qhasm: b3 = a3 +# asm 1: movdqa b3=int6464#7 +# asm 2: movdqa b3=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a3 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,a5=int6464#6 +# asm 2: movdqa a5=%xmm5 +movdqa %xmm0,%xmm5 + +# qhasm: b4 = a4 +# asm 1: movdqa b4=int6464#7 +# asm 2: movdqa b4=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a4 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,a6=int6464#5 +# asm 2: movdqa a6=%xmm4 +movdqa %xmm1,%xmm4 + +# qhasm: b5 = a5 +# asm 1: movdqa b5=int6464#7 +# asm 2: movdqa b5=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a5 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,a7=int6464#6 +# asm 2: movdqa a7=%xmm5 +movdqa %xmm2,%xmm5 + +# qhasm: b6 = a6 +# asm 1: movdqa b6=int6464#7 +# asm 2: movdqa b6=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a6 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,a0=int6464#5 +# asm 2: movdqa a0=%xmm4 +movdqa %xmm1,%xmm4 + +# qhasm: b7 = a7 +# asm 1: movdqa b7=int6464#7 +# asm 2: movdqa b7=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a7 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,a1=int6464#6 +# asm 2: movdqa a1=%xmm5 +movdqa %xmm0,%xmm5 + +# qhasm: b0 = a0 +# asm 1: movdqa b0=int6464#7 +# asm 2: movdqa b0=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a0 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,a2=int6464#5 +# asm 2: movdqa a2=%xmm4 +movdqa %xmm3,%xmm4 + +# qhasm: b1 = a1 +# asm 1: movdqa b1=int6464#7 +# asm 2: movdqa b1=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a1 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,a3=int6464#6 +# asm 2: movdqa a3=%xmm5 +movdqa %xmm2,%xmm5 + +# qhasm: b2 = a2 +# asm 1: movdqa b2=int6464#7 +# asm 2: movdqa b2=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a2 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,a4=int6464#5 +# asm 2: movdqa a4=%xmm4 +movdqa %xmm3,%xmm4 + +# qhasm: b3 = a3 +# asm 1: movdqa b3=int6464#7 +# asm 2: movdqa b3=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a3 <<= 18 +# asm 1: pslld $18,>= 14 +# asm 1: psrld $14,a5=int6464#6 +# asm 2: movdqa a5=%xmm5 +movdqa %xmm0,%xmm5 + +# qhasm: b4 = a4 +# asm 1: movdqa b4=int6464#7 +# asm 2: movdqa b4=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a4 <<= 7 +# asm 1: pslld $7,>= 25 +# asm 1: psrld $25,a6=int6464#5 +# asm 2: movdqa a6=%xmm4 +movdqa %xmm1,%xmm4 + +# qhasm: b5 = a5 +# asm 1: movdqa b5=int6464#7 +# asm 2: movdqa b5=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a5 <<= 9 +# asm 1: pslld $9,>= 23 +# asm 1: psrld $23,a7=int6464#6 +# asm 2: movdqa a7=%xmm5 +movdqa %xmm2,%xmm5 + +# qhasm: b6 = a6 +# asm 1: movdqa b6=int6464#7 +# asm 2: movdqa b6=%xmm6 +movdqa %xmm4,%xmm6 + +# qhasm: uint32323232 a6 <<= 13 +# asm 1: pslld $13,>= 19 +# asm 1: psrld $19,? i -= 4 +# asm 1: sub $4,a0=int6464#5 +# asm 2: movdqa a0=%xmm4 +movdqa %xmm1,%xmm4 + +# qhasm: b7 = a7 +# asm 1: movdqa b7=int6464#7 +# asm 2: movdqa b7=%xmm6 +movdqa %xmm5,%xmm6 + +# qhasm: uint32323232 a7 <<= 18 +# asm 1: pslld $18,b0=int6464#8,>b0=int6464#8 +# asm 2: pxor >b0=%xmm7,>b0=%xmm7 +pxor %xmm7,%xmm7 + +# qhasm: uint32323232 b7 >>= 14 +# asm 1: psrld $14, +ja ._mainloop2 + +# qhasm: uint32323232 diag0 += x0 +# asm 1: paddd in0=int64#4 +# asm 2: movd in0=%rcx +movd %xmm0,%rcx + +# qhasm: in12 = diag1 +# asm 1: movd in12=int64#5 +# asm 2: movd in12=%r8 +movd %xmm1,%r8 + +# qhasm: in8 = diag2 +# asm 1: movd in8=int64#6 +# asm 2: movd in8=%r9 +movd %xmm2,%r9 + +# qhasm: in4 = diag3 +# asm 1: movd in4=int64#7 +# asm 2: movd in4=%rax +movd %xmm3,%rax + +# qhasm: diag0 <<<= 96 +# asm 1: pshufd $0x39,in5=int64#4 +# asm 2: movd in5=%rcx +movd %xmm0,%rcx + +# qhasm: in1 = diag1 +# asm 1: movd in1=int64#5 +# asm 2: movd in1=%r8 +movd %xmm1,%r8 + +# qhasm: in13 = diag2 +# asm 1: movd in13=int64#6 +# asm 2: movd in13=%r9 +movd %xmm2,%r9 + +# qhasm: in9 = diag3 +# asm 1: movd in9=int64#7 +# asm 2: movd in9=%rax +movd %xmm3,%rax + +# qhasm: diag0 <<<= 96 +# asm 1: pshufd $0x39,in10=int64#4 +# asm 2: movd in10=%rcx +movd %xmm0,%rcx + +# qhasm: in6 = diag1 +# asm 1: movd in6=int64#5 +# asm 2: movd in6=%r8 +movd %xmm1,%r8 + +# qhasm: in2 = diag2 +# asm 1: movd in2=int64#6 +# asm 2: movd in2=%r9 +movd %xmm2,%r9 + +# qhasm: in14 = diag3 +# asm 1: movd in14=int64#7 +# asm 2: movd in14=%rax +movd %xmm3,%rax + +# qhasm: diag0 <<<= 96 +# asm 1: pshufd $0x39,in15=int64#4 +# asm 2: movd in15=%rcx +movd %xmm0,%rcx + +# qhasm: in11 = diag1 +# asm 1: movd in11=int64#5 +# asm 2: movd in11=%r8 +movd %xmm1,%r8 + +# qhasm: in7 = diag2 +# asm 1: movd in7=int64#6 +# asm 2: movd in7=%r9 +movd %xmm2,%r9 + +# qhasm: in3 = diag3 +# asm 1: movd in3=int64#7 +# asm 2: movd in3=%rax +movd %xmm3,%rax + +# qhasm: (uint32) in15 ^= *(uint32 *) (m + 60) +# asm 1: xorl 60(bytes=int64#6 +# asm 2: movq bytes=%r9 +movq 408(%rsp),%r9 + +# qhasm: in8 = ((uint32 *)&x2)[0] +# asm 1: movl in8=int64#4d +# asm 2: movl in8=%ecx +movl 16(%rsp),%ecx + +# qhasm: in9 = ((uint32 *)&x3)[1] +# asm 1: movl 4+in9=int64#5d +# asm 2: movl 4+in9=%r8d +movl 4+32(%rsp),%r8d + +# qhasm: in8 += 1 +# asm 1: add $1,in9=int64#5 +# asm 2: mov in9=%r8 +mov %rcx,%r8 + +# qhasm: (uint64) in9 >>= 32 +# asm 1: shr $32,x2=stack128#2 +# asm 2: movl x2=16(%rsp) +movl %ecx,16(%rsp) + +# qhasm: ((uint32 *)&x3)[1] = in9 +# asm 1: movl ? unsigned +ja ._bytesatleast65 +# comment:fp stack unchanged by jump + +# qhasm: goto bytesatleast64 if !unsigned< +jae ._bytesatleast64 + +# qhasm: m = out +# asm 1: mov m=int64#2 +# asm 2: mov m=%rsi +mov %rdi,%rsi + +# qhasm: out = ctarget +# asm 1: mov out=int64#1 +# asm 2: mov out=%rdi +mov %rdx,%rdi + +# qhasm: i = bytes +# asm 1: mov i=int64#4 +# asm 2: mov i=%rcx +mov %r9,%rcx + +# qhasm: while (i) { *out++ = *m++; --i } +rep movsb +# comment:fp stack unchanged by fallthrough + +# qhasm: bytesatleast64: +._bytesatleast64: +# comment:fp stack unchanged by fallthrough + +# qhasm: done: +._done: + +# qhasm: r11_caller = r11_stack +# asm 1: movq r11_caller=int64#9 +# asm 2: movq r11_caller=%r11 +movq 352(%rsp),%r11 + +# qhasm: r12_caller = r12_stack +# asm 1: movq r12_caller=int64#10 +# asm 2: movq r12_caller=%r12 +movq 360(%rsp),%r12 + +# qhasm: r13_caller = r13_stack +# asm 1: movq r13_caller=int64#11 +# asm 2: movq r13_caller=%r13 +movq 368(%rsp),%r13 + +# qhasm: r14_caller = r14_stack +# asm 1: movq r14_caller=int64#12 +# asm 2: movq r14_caller=%r14 +movq 376(%rsp),%r14 + +# qhasm: r15_caller = r15_stack +# asm 1: movq r15_caller=int64#13 +# asm 2: movq r15_caller=%r15 +movq 384(%rsp),%r15 + +# qhasm: rbx_caller = rbx_stack +# asm 1: movq rbx_caller=int64#14 +# asm 2: movq rbx_caller=%rbx +movq 392(%rsp),%rbx + +# qhasm: rbp_caller = rbp_stack +# asm 1: movq rbp_caller=int64#15 +# asm 2: movq rbp_caller=%rbp +movq 400(%rsp),%rbp + +# qhasm: leave +add %r11,%rsp +xor %rax,%rax +xor %rdx,%rdx +ret + +# qhasm: bytesatleast65: +._bytesatleast65: + +# qhasm: bytes -= 64 +# asm 1: sub $64,. + */ + +/* + * This defines the external C API for ZeroTier's core network virtualization + * engine. + */ + +#ifndef ZT_ZEROTIERONE_H +#define ZT_ZEROTIERONE_H + +#include + +// For the struct sockaddr_storage structure +#if defined(_WIN32) || defined(_WIN64) +#include +#include +#include +#else /* not Windows */ +#include +#include +#include +#include +#endif /* Windows or not */ + +#ifdef __cplusplus +extern "C" { +#endif + +/****************************************************************************/ +/* Core constants */ +/****************************************************************************/ + +/** + * Default UDP port for devices running a ZeroTier endpoint + */ +#define ZT_DEFAULT_PORT 9993 + +/** + * Maximum MTU for ZeroTier virtual networks + * + * This is pretty much an unchangeable global constant. To make it change + * across nodes would require logic to send ICMP packet too big messages, + * which would complicate things. 1500 has been good enough on most LANs + * for ages, so a larger MTU should be fine for the forseeable future. This + * typically results in two UDP packets per single large frame. Experimental + * results seem to show that this is good. Larger MTUs resulting in more + * fragments seemed too brittle on slow/crummy links for no benefit. + * + * If this does change, also change it in tap.h in the tuntaposx code under + * mac-tap. + * + * Overhead for a normal frame split into two packets: + * + * 1414 = 1444 (typical UDP MTU) - 28 (packet header) - 2 (ethertype) + * 1428 = 1444 (typical UDP MTU) - 16 (fragment header) + * SUM: 2842 + * + * We use 2800, which leaves some room for other payload in other types of + * messages such as multicast propagation or future support for bridging. + */ +#define ZT_MAX_MTU 2800 + +/** + * Maximum length of network short name + */ +#define ZT_MAX_NETWORK_SHORT_NAME_LENGTH 127 + +/** + * Maximum number of pushed routes on a network + */ +#define ZT_MAX_NETWORK_ROUTES 32 + +/** + * Maximum number of statically assigned IP addresses per network endpoint using ZT address management (not DHCP) + */ +#define ZT_MAX_ZT_ASSIGNED_ADDRESSES 16 + +/** + * Maximum number of "specialists" on a network -- bridges, relays, etc. + */ +#define ZT_MAX_NETWORK_SPECIALISTS 256 + +/** + * Maximum number of multicast group subscriptions per network + */ +#define ZT_MAX_NETWORK_MULTICAST_SUBSCRIPTIONS 4096 + +/** + * Rules engine revision ID, which specifies rules engine capabilities + */ +#define ZT_RULES_ENGINE_REVISION 1 + +/** + * Maximum number of base (non-capability) network rules + */ +#define ZT_MAX_NETWORK_RULES 1024 + +/** + * Maximum number of per-member capabilities per network + */ +#define ZT_MAX_NETWORK_CAPABILITIES 128 + +/** + * Maximum number of per-member tags per network + */ +#define ZT_MAX_NETWORK_TAGS 128 + +/** + * Maximum number of direct network paths to a given peer + */ +#define ZT_MAX_PEER_NETWORK_PATHS 4 + +/** + * Maximum number of trusted physical network paths + */ +#define ZT_MAX_TRUSTED_PATHS 16 + +/** + * Maximum number of rules per capability + */ +#define ZT_MAX_CAPABILITY_RULES 64 + +/** + * Maximum number of certificates of ownership to assign to a single network member + */ +#define ZT_MAX_CERTIFICATES_OF_OWNERSHIP 4 + +/** + * Global maximum length for capability chain of custody (including initial issue) + */ +#define ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH 7 + +/** + * Maximum number of hops in a ZeroTier circuit test + * + * This is more or less the max that can be fit in a given packet (with + * fragmentation) and only one address per hop. + */ +#define ZT_CIRCUIT_TEST_MAX_HOPS 256 + +/** + * Maximum number of addresses per hop in a circuit test + */ +#define ZT_CIRCUIT_TEST_MAX_HOP_BREADTH 8 + +/** + * Circuit test report flag: upstream peer authorized in path (e.g. by network COM) + */ +#define ZT_CIRCUIT_TEST_REPORT_FLAGS_UPSTREAM_AUTHORIZED_IN_PATH 0x0000000000000001ULL + +/** + * Maximum number of cluster members (and max member ID plus one) + */ +#define ZT_CLUSTER_MAX_MEMBERS 128 + +/** + * Maximum number of physical ZeroTier addresses a cluster member can report + */ +#define ZT_CLUSTER_MAX_ZT_PHYSICAL_ADDRESSES 16 + +/** + * Maximum allowed cluster message length in bytes + */ +#define ZT_CLUSTER_MAX_MESSAGE_LENGTH (1500 - 48) + +/** + * Maximum value for link quality (min is 0) + */ +#define ZT_PATH_LINK_QUALITY_MAX 0xff + +/** + * Packet characteristics flag: packet direction, 1 if inbound 0 if outbound + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_INBOUND 0x8000000000000000ULL + +/** + * Packet characteristics flag: multicast or broadcast destination MAC + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_MULTICAST 0x4000000000000000ULL + +/** + * Packet characteristics flag: broadcast destination MAC + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST 0x2000000000000000ULL + +/** + * Packet characteristics flag: sending IP address has a certificate of ownership + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED 0x1000000000000000ULL + +/** + * Packet characteristics flag: sending MAC address has a certificate of ownership + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_SENDER_MAC_AUTHENTICATED 0x0800000000000000ULL + +/** + * Packet characteristics flag: TCP left-most reserved bit + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_TCP_RESERVED_0 0x0000000000000800ULL + +/** + * Packet characteristics flag: TCP middle reserved bit + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_TCP_RESERVED_1 0x0000000000000400ULL + +/** + * Packet characteristics flag: TCP right-most reserved bit + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_TCP_RESERVED_2 0x0000000000000200ULL + +/** + * Packet characteristics flag: TCP NS flag + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_TCP_NS 0x0000000000000100ULL + +/** + * Packet characteristics flag: TCP CWR flag + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_TCP_CWR 0x0000000000000080ULL + +/** + * Packet characteristics flag: TCP ECE flag + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_TCP_ECE 0x0000000000000040ULL + +/** + * Packet characteristics flag: TCP URG flag + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_TCP_URG 0x0000000000000020ULL + +/** + * Packet characteristics flag: TCP ACK flag + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_TCP_ACK 0x0000000000000010ULL + +/** + * Packet characteristics flag: TCP PSH flag + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_TCP_PSH 0x0000000000000008ULL + +/** + * Packet characteristics flag: TCP RST flag + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_TCP_RST 0x0000000000000004ULL + +/** + * Packet characteristics flag: TCP SYN flag + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_TCP_SYN 0x0000000000000002ULL + +/** + * Packet characteristics flag: TCP FIN flag + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_TCP_FIN 0x0000000000000001ULL + +/** + * A null/empty sockaddr (all zero) to signify an unspecified socket address + */ +extern const struct sockaddr_storage ZT_SOCKADDR_NULL; + +/****************************************************************************/ +/* Structures and other types */ +/****************************************************************************/ + +/** + * Function return code: OK (0) or error results + * + * Use ZT_ResultCode_isFatal() to check for a fatal error. If a fatal error + * occurs, the node should be considered to not be working correctly. These + * indicate serious problems like an inaccessible data store or a compile + * problem. + */ +enum ZT_ResultCode +{ + /** + * Operation completed normally + */ + ZT_RESULT_OK = 0, + + // Fatal errors (>0, <1000) + + /** + * Ran out of memory + */ + ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY = 1, + + /** + * Data store is not writable or has failed + */ + ZT_RESULT_FATAL_ERROR_DATA_STORE_FAILED = 2, + + /** + * Internal error (e.g. unexpected exception indicating bug or build problem) + */ + ZT_RESULT_FATAL_ERROR_INTERNAL = 3, + + // Non-fatal errors (>1000) + + /** + * Network ID not valid + */ + ZT_RESULT_ERROR_NETWORK_NOT_FOUND = 1000, + + /** + * The requested operation is not supported on this version or build + */ + ZT_RESULT_ERROR_UNSUPPORTED_OPERATION = 1001, + + /** + * The requestion operation was given a bad parameter or was called in an invalid state + */ + ZT_RESULT_ERROR_BAD_PARAMETER = 1002 +}; + +/** + * @param x Result code + * @return True if result code indicates a fatal error + */ +#define ZT_ResultCode_isFatal(x) ((((int)(x)) > 0)&&(((int)(x)) < 1000)) + +/** + * Status codes sent to status update callback when things happen + */ +enum ZT_Event +{ + /** + * Node has been initialized + * + * This is the first event generated, and is always sent. It may occur + * before Node's constructor returns. + * + * Meta-data: none + */ + ZT_EVENT_UP = 0, + + /** + * Node is offline -- network does not seem to be reachable by any available strategy + * + * Meta-data: none + */ + ZT_EVENT_OFFLINE = 1, + + /** + * Node is online -- at least one upstream node appears reachable + * + * Meta-data: none + */ + ZT_EVENT_ONLINE = 2, + + /** + * Node is shutting down + * + * This is generated within Node's destructor when it is being shut down. + * It's done for convenience, since cleaning up other state in the event + * handler may appear more idiomatic. + * + * Meta-data: none + */ + ZT_EVENT_DOWN = 3, + + /** + * Your identity has collided with another node's ZeroTier address + * + * This happens if two different public keys both hash (via the algorithm + * in Identity::generate()) to the same 40-bit ZeroTier address. + * + * This is something you should "never" see, where "never" is defined as + * once per 2^39 new node initializations / identity creations. If you do + * see it, you're going to see it very soon after a node is first + * initialized. + * + * This is reported as an event rather than a return code since it's + * detected asynchronously via error messages from authoritative nodes. + * + * If this occurs, you must shut down and delete the node, delete the + * identity.secret record/file from the data store, and restart to generate + * a new identity. If you don't do this, you will not be able to communicate + * with other nodes. + * + * We'd automate this process, but we don't think silently deleting + * private keys or changing our address without telling the calling code + * is good form. It violates the principle of least surprise. + * + * You can technically get away with not handling this, but we recommend + * doing so in a mature reliable application. Besides, handling this + * condition is a good way to make sure it never arises. It's like how + * umbrellas prevent rain and smoke detectors prevent fires. They do, right? + * + * Meta-data: none + */ + ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION = 4, + + /** + * Trace (debugging) message + * + * These events are only generated if this is a TRACE-enabled build. + * + * Meta-data: C string, TRACE message + */ + ZT_EVENT_TRACE = 5, + + /** + * VERB_USER_MESSAGE received + * + * These are generated when a VERB_USER_MESSAGE packet is received via + * ZeroTier VL1. + * + * Meta-data: ZT_UserMessage structure + */ + ZT_EVENT_USER_MESSAGE = 6 +}; + +/** + * User message used with ZT_EVENT_USER_MESSAGE + */ +typedef struct +{ + /** + * ZeroTier address of sender (least significant 40 bits) + */ + uint64_t origin; + + /** + * User message type ID + */ + uint64_t typeId; + + /** + * User message data (not including type ID) + */ + const void *data; + + /** + * Length of data in bytes + */ + unsigned int length; +} ZT_UserMessage; + +/** + * Current node status + */ +typedef struct +{ + /** + * 40-bit ZeroTier address of this node + */ + uint64_t address; + + /** + * Public identity in string-serialized form (safe to send to others) + * + * This pointer will remain valid as long as the node exists. + */ + const char *publicIdentity; + + /** + * Full identity including secret key in string-serialized form + * + * This pointer will remain valid as long as the node exists. + */ + const char *secretIdentity; + + /** + * True if some kind of connectivity appears available + */ + int online; +} ZT_NodeStatus; + +/** + * Virtual network status codes + */ +enum ZT_VirtualNetworkStatus +{ + /** + * Waiting for network configuration (also means revision == 0) + */ + ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION = 0, + + /** + * Configuration received and we are authorized + */ + ZT_NETWORK_STATUS_OK = 1, + + /** + * Netconf master told us 'nope' + */ + ZT_NETWORK_STATUS_ACCESS_DENIED = 2, + + /** + * Netconf master exists, but this virtual network does not + */ + ZT_NETWORK_STATUS_NOT_FOUND = 3, + + /** + * Initialization of network failed or other internal error + */ + ZT_NETWORK_STATUS_PORT_ERROR = 4, + + /** + * ZeroTier core version too old + */ + ZT_NETWORK_STATUS_CLIENT_TOO_OLD = 5 +}; + +/** + * Virtual network type codes + */ +enum ZT_VirtualNetworkType +{ + /** + * Private networks are authorized via certificates of membership + */ + ZT_NETWORK_TYPE_PRIVATE = 0, + + /** + * Public networks have no access control -- they'll always be AUTHORIZED + */ + ZT_NETWORK_TYPE_PUBLIC = 1 +}; + +/** + * The type of a virtual network rules table entry + * + * These must be from 0 to 63 since the most significant two bits of each + * rule type are NOT (MSB) and AND/OR. + * + * Each rule is composed of zero or more MATCHes followed by an ACTION. + * An ACTION with no MATCHes is always taken. + */ +enum ZT_VirtualNetworkRuleType +{ + // 0 to 15 reserved for actions + + /** + * Drop frame + */ + ZT_NETWORK_RULE_ACTION_DROP = 0, + + /** + * Accept and pass frame + */ + ZT_NETWORK_RULE_ACTION_ACCEPT = 1, + + /** + * Forward a copy of this frame to an observer (by ZT address) + */ + ZT_NETWORK_RULE_ACTION_TEE = 2, + + /** + * Exactly like TEE but mandates ACKs from observer + */ + ZT_NETWORK_RULE_ACTION_WATCH = 3, + + /** + * Drop and redirect this frame to another node (by ZT address) + */ + ZT_NETWORK_RULE_ACTION_REDIRECT = 4, + + /** + * Stop evaluating rule set (drops unless there are capabilities, etc.) + */ + ZT_NETWORK_RULE_ACTION_BREAK = 5, + + /** + * Maximum ID for an ACTION, anything higher is a MATCH + */ + ZT_NETWORK_RULE_ACTION__MAX_ID = 15, + + // 16 to 63 reserved for match criteria + + ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS = 24, + ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS = 25, + ZT_NETWORK_RULE_MATCH_VLAN_ID = 26, + ZT_NETWORK_RULE_MATCH_VLAN_PCP = 27, + ZT_NETWORK_RULE_MATCH_VLAN_DEI = 28, + ZT_NETWORK_RULE_MATCH_MAC_SOURCE = 29, + ZT_NETWORK_RULE_MATCH_MAC_DEST = 30, + ZT_NETWORK_RULE_MATCH_IPV4_SOURCE = 31, + ZT_NETWORK_RULE_MATCH_IPV4_DEST = 32, + ZT_NETWORK_RULE_MATCH_IPV6_SOURCE = 33, + ZT_NETWORK_RULE_MATCH_IPV6_DEST = 34, + ZT_NETWORK_RULE_MATCH_IP_TOS = 35, + ZT_NETWORK_RULE_MATCH_IP_PROTOCOL = 36, + ZT_NETWORK_RULE_MATCH_ETHERTYPE = 37, + ZT_NETWORK_RULE_MATCH_ICMP = 38, + ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE = 39, + ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE = 40, + ZT_NETWORK_RULE_MATCH_CHARACTERISTICS = 41, + ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE = 42, + ZT_NETWORK_RULE_MATCH_RANDOM = 43, + ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE = 44, + ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND = 45, + ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR = 46, + ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR = 47, + ZT_NETWORK_RULE_MATCH_TAGS_EQUAL = 48, + ZT_NETWORK_RULE_MATCH_TAG_SENDER = 49, + ZT_NETWORK_RULE_MATCH_TAG_RECEIVER = 50, + + /** + * Maximum ID allowed for a MATCH entry in the rules table + */ + ZT_NETWORK_RULE_MATCH__MAX_ID = 63 +}; + +/** + * Network flow rule + * + * Rules are stored in a table in which one or more match entries is followed + * by an action. If more than one match precedes an action, the rule is + * the AND of all matches. An action with no match is always taken since it + * matches anything. If nothing matches, the default action is DROP. + * + * This is designed to be a more memory-efficient way of storing rules than + * a wide table, yet still fast and simple to access in code. + */ +typedef struct +{ + /** + * Type and flags + * + * Bits are: NOTTTTTT + * + * N - If true, sense of match is inverted (no effect on actions) + * O - If true, result is ORed with previous instead of ANDed (no effect on actions) + * T - Rule or action type + * + * AND with 0x3f to get type, 0x80 to get NOT bit, and 0x40 to get OR bit. + */ + uint8_t t; + + /** + * Union containing the value of this rule -- which field is used depends on 't' + */ + union { + /** + * IPv6 address in big-endian / network byte order and netmask bits + */ + struct { + uint8_t ip[16]; + uint8_t mask; + } ipv6; + + /** + * IPv4 address in big-endian / network byte order + */ + struct { + uint32_t ip; + uint8_t mask; + } ipv4; + + /** + * Packet characteristic flags being matched + */ + uint64_t characteristics; + + /** + * IP port range -- start-end inclusive -- host byte order + */ + uint16_t port[2]; + + /** + * 40-bit ZeroTier address (in least significant bits, host byte order) + */ + uint64_t zt; + + /** + * 0 = never, UINT32_MAX = always + */ + uint32_t randomProbability; + + /** + * 48-bit Ethernet MAC address in big-endian order + */ + uint8_t mac[6]; + + /** + * VLAN ID in host byte order + */ + uint16_t vlanId; + + /** + * VLAN PCP (least significant 3 bits) + */ + uint8_t vlanPcp; + + /** + * VLAN DEI (single bit / boolean) + */ + uint8_t vlanDei; + + /** + * Ethernet type in host byte order + */ + uint16_t etherType; + + /** + * IP protocol + */ + uint8_t ipProtocol; + + /** + * IP type of service a.k.a. DSCP field + */ + struct { + uint8_t mask; + uint8_t value[2]; + } ipTos; + + /** + * Ethernet packet size in host byte order (start-end, inclusive) + */ + uint16_t frameSize[2]; + + /** + * ICMP type and code + */ + struct { + uint8_t type; // ICMP type, always matched + uint8_t code; // ICMP code if matched + uint8_t flags; // flag 0x01 means also match code, otherwise only match type + } icmp; + + /** + * For tag-related rules + */ + struct { + uint32_t id; + uint32_t value; + } tag; + + /** + * Destinations for TEE and REDIRECT + */ + struct { + uint64_t address; + uint32_t flags; + uint16_t length; + } fwd; + } v; +} ZT_VirtualNetworkRule; + +typedef struct +{ + /** + * 128-bit ID (GUID) of this capability + */ + uint64_t id[2]; + + /** + * Expiration time (measured vs. network config timestamp issued by controller) + */ + uint64_t expiration; + + + struct { + uint64_t from; + uint64_t to; + } custody[ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH]; +} ZT_VirtualNetworkCapability; + +/** + * A route to be pushed on a virtual network + */ +typedef struct +{ + /** + * Target network / netmask bits (in port field) or NULL or 0.0.0.0/0 for default + */ + struct sockaddr_storage target; + + /** + * Gateway IP address (port ignored) or NULL (family == 0) for LAN-local (no gateway) + */ + struct sockaddr_storage via; + + /** + * Route flags + */ + uint16_t flags; + + /** + * Route metric (not currently used) + */ + uint16_t metric; +} ZT_VirtualNetworkRoute; + +/** + * An Ethernet multicast group + */ +typedef struct +{ + /** + * MAC address (least significant 48 bits) + */ + uint64_t mac; + + /** + * Additional distinguishing information (usually zero) + */ + unsigned long adi; +} ZT_MulticastGroup; + +/** + * Virtual network configuration update type + */ +enum ZT_VirtualNetworkConfigOperation +{ + /** + * Network is coming up (either for the first time or after service restart) + */ + ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP = 1, + + /** + * Network configuration has been updated + */ + ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE = 2, + + /** + * Network is going down (not permanently) + */ + ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN = 3, + + /** + * Network is going down permanently (leave/delete) + */ + ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY = 4 +}; + +/** + * What trust hierarchy role does this peer have? + */ +enum ZT_PeerRole +{ + ZT_PEER_ROLE_LEAF = 0, // ordinary node + ZT_PEER_ROLE_MOON = 1, // moon root + ZT_PEER_ROLE_PLANET = 2 // planetary root +}; + +/** + * Vendor ID + */ +enum ZT_Vendor +{ + ZT_VENDOR_UNSPECIFIED = 0, + ZT_VENDOR_ZEROTIER = 1 +}; + +/** + * Platform type + */ +enum ZT_Platform +{ + ZT_PLATFORM_UNSPECIFIED = 0, + ZT_PLATFORM_LINUX = 1, + ZT_PLATFORM_WINDOWS = 2, + ZT_PLATFORM_MACOS = 3, + ZT_PLATFORM_ANDROID = 4, + ZT_PLATFORM_IOS = 5, + ZT_PLATFORM_SOLARIS_SMARTOS = 6, + ZT_PLATFORM_FREEBSD = 7, + ZT_PLATFORM_NETBSD = 8, + ZT_PLATFORM_OPENBSD = 9, + ZT_PLATFORM_RISCOS = 10, + ZT_PLATFORM_VXWORKS = 11, + ZT_PLATFORM_FREERTOS = 12, + ZT_PLATFORM_SYSBIOS = 13, + ZT_PLATFORM_HURD = 14, + ZT_PLATFORM_WEB = 15 +}; + +/** + * Architecture type + */ +enum ZT_Architecture +{ + ZT_ARCHITECTURE_UNSPECIFIED = 0, + ZT_ARCHITECTURE_X86 = 1, + ZT_ARCHITECTURE_X64 = 2, + ZT_ARCHITECTURE_ARM32 = 3, + ZT_ARCHITECTURE_ARM64 = 4, + ZT_ARCHITECTURE_MIPS32 = 5, + ZT_ARCHITECTURE_MIPS64 = 6, + ZT_ARCHITECTURE_POWER32 = 7, + ZT_ARCHITECTURE_POWER64 = 8, + ZT_ARCHITECTURE_OPENRISC32 = 9, + ZT_ARCHITECTURE_OPENRISC64 = 10, + ZT_ARCHITECTURE_SPARC32 = 11, + ZT_ARCHITECTURE_SPARC64 = 12, + ZT_ARCHITECTURE_DOTNET_CLR = 13, + ZT_ARCHITECTURE_JAVA_JVM = 14, + ZT_ARCHITECTURE_WEB = 15 +}; + +/** + * Virtual network configuration + */ +typedef struct +{ + /** + * 64-bit ZeroTier network ID + */ + uint64_t nwid; + + /** + * Ethernet MAC (48 bits) that should be assigned to port + */ + uint64_t mac; + + /** + * Network name (from network configuration master) + */ + char name[ZT_MAX_NETWORK_SHORT_NAME_LENGTH + 1]; + + /** + * Network configuration request status + */ + enum ZT_VirtualNetworkStatus status; + + /** + * Network type + */ + enum ZT_VirtualNetworkType type; + + /** + * Maximum interface MTU + */ + unsigned int mtu; + + /** + * Recommended MTU to avoid fragmentation at the physical layer (hint) + */ + unsigned int physicalMtu; + + /** + * If nonzero, the network this port belongs to indicates DHCP availability + * + * This is a suggestion. The underlying implementation is free to ignore it + * for security or other reasons. This is simply a netconf parameter that + * means 'DHCP is available on this network.' + */ + int dhcp; + + /** + * If nonzero, this port is allowed to bridge to other networks + * + * This is informational. If this is false (0), bridged packets will simply + * be dropped and bridging won't work. + */ + int bridge; + + /** + * If nonzero, this network supports and allows broadcast (ff:ff:ff:ff:ff:ff) traffic + */ + int broadcastEnabled; + + /** + * If the network is in PORT_ERROR state, this is the (negative) error code most recently reported + */ + int portError; + + /** + * Revision number as reported by controller or 0 if still waiting for config + */ + unsigned long netconfRevision; + + /** + * Number of assigned addresses + */ + unsigned int assignedAddressCount; + + /** + * ZeroTier-assigned addresses (in sockaddr_storage structures) + * + * For IP, the port number of the sockaddr_XX structure contains the number + * of bits in the address netmask. Only the IP address and port are used. + * Other fields like interface number can be ignored. + * + * This is only used for ZeroTier-managed address assignments sent by the + * virtual network's configuration master. + */ + struct sockaddr_storage assignedAddresses[ZT_MAX_ZT_ASSIGNED_ADDRESSES]; + + /** + * Number of ZT-pushed routes + */ + unsigned int routeCount; + + /** + * Routes (excluding those implied by assigned addresses and their masks) + */ + ZT_VirtualNetworkRoute routes[ZT_MAX_NETWORK_ROUTES]; +} ZT_VirtualNetworkConfig; + +/** + * A list of networks + */ +typedef struct +{ + ZT_VirtualNetworkConfig *networks; + unsigned long networkCount; +} ZT_VirtualNetworkList; + +/** + * Physical network path to a peer + */ +typedef struct +{ + /** + * Address of endpoint + */ + struct sockaddr_storage address; + + /** + * Time of last send in milliseconds or 0 for never + */ + uint64_t lastSend; + + /** + * Time of last receive in milliseconds or 0 for never + */ + uint64_t lastReceive; + + /** + * Is this a trusted path? If so this will be its nonzero ID. + */ + uint64_t trustedPathId; + + /** + * Path link quality from 0 to 255 (always 255 if peer does not support) + */ + int linkQuality; + + /** + * Is path expired? + */ + int expired; + + /** + * Is path preferred? + */ + int preferred; +} ZT_PeerPhysicalPath; + +/** + * Peer status result buffer + */ +typedef struct +{ + /** + * ZeroTier address (40 bits) + */ + uint64_t address; + + /** + * Remote major version or -1 if not known + */ + int versionMajor; + + /** + * Remote minor version or -1 if not known + */ + int versionMinor; + + /** + * Remote revision or -1 if not known + */ + int versionRev; + + /** + * Last measured latency in milliseconds or zero if unknown + */ + unsigned int latency; + + /** + * What trust hierarchy role does this device have? + */ + enum ZT_PeerRole role; + + /** + * Number of paths (size of paths[]) + */ + unsigned int pathCount; + + /** + * Known network paths to peer + */ + ZT_PeerPhysicalPath paths[ZT_MAX_PEER_NETWORK_PATHS]; +} ZT_Peer; + +/** + * List of peers + */ +typedef struct +{ + ZT_Peer *peers; + unsigned long peerCount; +} ZT_PeerList; + +/** + * ZeroTier circuit test configuration and path + */ +typedef struct { + /** + * Test ID -- an arbitrary 64-bit identifier + */ + uint64_t testId; + + /** + * Timestamp -- sent with test and echoed back by each reporter + */ + uint64_t timestamp; + + /** + * Originator credential: network ID + * + * If this is nonzero, a network ID will be set for this test and + * the originator must be its primary network controller. This is + * currently the only authorization method available, so it must + * be set to run a test. + */ + uint64_t credentialNetworkId; + + /** + * Hops in circuit test (a.k.a. FIFO for graph traversal) + */ + struct { + /** + * Hop flags (currently unused, must be zero) + */ + unsigned int flags; + + /** + * Number of addresses in this hop (max: ZT_CIRCUIT_TEST_MAX_HOP_BREADTH) + */ + unsigned int breadth; + + /** + * 40-bit ZeroTier addresses (most significant 24 bits ignored) + */ + uint64_t addresses[ZT_CIRCUIT_TEST_MAX_HOP_BREADTH]; + } hops[ZT_CIRCUIT_TEST_MAX_HOPS]; + + /** + * Number of hops (max: ZT_CIRCUIT_TEST_MAX_HOPS) + */ + unsigned int hopCount; + + /** + * If non-zero, circuit test will report back at every hop + */ + int reportAtEveryHop; + + /** + * An arbitrary user-settable pointer + */ + void *ptr; + + /** + * Pointer for internal use -- initialize to zero and do not modify + */ + void *_internalPtr; +} ZT_CircuitTest; + +/** + * Circuit test result report + */ +typedef struct { + /** + * Sender of report (current hop) + */ + uint64_t current; + + /** + * Previous hop + */ + uint64_t upstream; + + /** + * 64-bit test ID + */ + uint64_t testId; + + /** + * Timestamp from original test (echoed back at each hop) + */ + uint64_t timestamp; + + /** + * 64-bit packet ID of packet received by the reporting device + */ + uint64_t sourcePacketId; + + /** + * Flags + */ + uint64_t flags; + + /** + * ZeroTier protocol-level hop count of packet received by reporting device (>0 indicates relayed) + */ + unsigned int sourcePacketHopCount; + + /** + * Error code (currently unused, will be zero) + */ + unsigned int errorCode; + + /** + * Remote device vendor ID + */ + enum ZT_Vendor vendor; + + /** + * Remote device protocol compliance version + */ + unsigned int protocolVersion; + + /** + * Software major version + */ + unsigned int majorVersion; + + /** + * Software minor version + */ + unsigned int minorVersion; + + /** + * Software revision + */ + unsigned int revision; + + /** + * Platform / OS + */ + enum ZT_Platform platform; + + /** + * System architecture + */ + enum ZT_Architecture architecture; + + /** + * Local device address on which packet was received by reporting device + * + * This may have ss_family equal to zero (null address) if unspecified. + */ + struct sockaddr_storage receivedOnLocalAddress; + + /** + * Remote address from which reporter received the test packet + * + * This may have ss_family set to zero (null address) if unspecified. + */ + struct sockaddr_storage receivedFromRemoteAddress; + + /** + * Path link quality of physical path over which test was received + */ + int receivedFromLinkQuality; + + /** + * Next hops to which packets are being or will be sent by the reporter + * + * In addition to reporting back, the reporter may send the test on if + * there are more recipients in the FIFO. If it does this, it can report + * back the address(es) that make up the next hop and the physical address + * for each if it has one. The physical address being null/unspecified + * typically indicates that no direct path exists and the next packet + * will be relayed. + */ + struct { + /** + * 40-bit ZeroTier address + */ + uint64_t address; + + /** + * Physical address or null address (ss_family == 0) if unspecified or unknown + */ + struct sockaddr_storage physicalAddress; + } nextHops[ZT_CIRCUIT_TEST_MAX_HOP_BREADTH]; + + /** + * Number of next hops reported in nextHops[] + */ + unsigned int nextHopCount; +} ZT_CircuitTestReport; + +/** + * A cluster member's status + */ +typedef struct { + /** + * This cluster member's ID (from 0 to 1-ZT_CLUSTER_MAX_MEMBERS) + */ + unsigned int id; + + /** + * Number of milliseconds since last 'alive' heartbeat message received via cluster backplane address + */ + unsigned int msSinceLastHeartbeat; + + /** + * Non-zero if cluster member is alive + */ + int alive; + + /** + * X, Y, and Z coordinates of this member (if specified, otherwise zero) + * + * What these mean depends on the location scheme being used for + * location-aware clustering. At present this is GeoIP and these + * will be the X, Y, and Z coordinates of the location on a spherical + * approximation of Earth where Earth's core is the origin (in km). + * They don't have to be perfect and need only be comparable with others + * to find shortest path via the standard vector distance formula. + */ + int x,y,z; + + /** + * Cluster member's last reported load + */ + uint64_t load; + + /** + * Number of peers + */ + uint64_t peers; + + /** + * Physical ZeroTier endpoints for this member (where peers are sent when directed here) + */ + struct sockaddr_storage zeroTierPhysicalEndpoints[ZT_CLUSTER_MAX_ZT_PHYSICAL_ADDRESSES]; + + /** + * Number of physical ZeroTier endpoints this member is announcing + */ + unsigned int numZeroTierPhysicalEndpoints; +} ZT_ClusterMemberStatus; + +/** + * ZeroTier cluster status + */ +typedef struct { + /** + * My cluster member ID (a record for 'self' is included in member[]) + */ + unsigned int myId; + + /** + * Number of cluster members + */ + unsigned int clusterSize; + + /** + * Cluster member statuses + */ + ZT_ClusterMemberStatus members[ZT_CLUSTER_MAX_MEMBERS]; +} ZT_ClusterStatus; + +/** + * An instance of a ZeroTier One node (opaque) + */ +typedef void ZT_Node; + +/****************************************************************************/ +/* Callbacks used by Node API */ +/****************************************************************************/ + +/** + * Callback called to update virtual network port configuration + * + * This can be called at any time to update the configuration of a virtual + * network port. The parameter after the network ID specifies whether this + * port is being brought up, updated, brought down, or permanently deleted. + * + * This in turn should be used by the underlying implementation to create + * and configure tap devices at the OS (or virtual network stack) layer. + * + * The supplied config pointer is not guaranteed to remain valid, so make + * a copy if you want one. + * + * This should not call multicastSubscribe() or other network-modifying + * methods, as this could cause a deadlock in multithreaded or interrupt + * driven environments. + * + * This must return 0 on success. It can return any OS-dependent error code + * on failure, and this results in the network being placed into the + * PORT_ERROR state. + */ +typedef int (*ZT_VirtualNetworkConfigFunction)( + ZT_Node *, /* Node */ + void *, /* User ptr */ + void *, /* Thread ptr */ + uint64_t, /* Network ID */ + void **, /* Modifiable network user PTR */ + enum ZT_VirtualNetworkConfigOperation, /* Config operation */ + const ZT_VirtualNetworkConfig *); /* Network configuration */ + +/** + * Function to send a frame out to a virtual network port + * + * Parameters: (1) node, (2) user ptr, (3) network ID, (4) source MAC, + * (5) destination MAC, (6) ethertype, (7) VLAN ID, (8) frame data, + * (9) frame length. + */ +typedef void (*ZT_VirtualNetworkFrameFunction)( + ZT_Node *, /* Node */ + void *, /* User ptr */ + void *, /* Thread ptr */ + uint64_t, /* Network ID */ + void **, /* Modifiable network user PTR */ + uint64_t, /* Source MAC */ + uint64_t, /* Destination MAC */ + unsigned int, /* Ethernet type */ + unsigned int, /* VLAN ID (0 for none) */ + const void *, /* Frame data */ + unsigned int); /* Frame length */ + +/** + * Callback for events + * + * Events are generated when the node's status changes in a significant way + * and on certain non-fatal errors and events of interest. The final void + * parameter points to event meta-data. The type of event meta-data (and + * whether it is present at all) is event type dependent. See the comments + * in the definition of ZT_Event. + */ +typedef void (*ZT_EventCallback)( + ZT_Node *, /* Node */ + void *, /* User ptr */ + void *, /* Thread ptr */ + enum ZT_Event, /* Event type */ + const void *); /* Event payload (if applicable) */ + +/** + * Function to get an object from the data store + * + * Parameters: (1) object name, (2) buffer to fill, (3) size of buffer, (4) + * index in object to start reading, (5) result parameter that must be set + * to the actual size of the object if it exists. + * + * Object names can contain forward slash (/) path separators. They will + * never contain .. or backslash (\), so this is safe to map as a Unix-style + * path if the underlying storage permits. For security reasons we recommend + * returning errors if .. or \ are used. + * + * The function must return the actual number of bytes read. If the object + * doesn't exist, it should return -1. -2 should be returned on other errors + * such as errors accessing underlying storage. + * + * If the read doesn't fit in the buffer, the max number of bytes should be + * read. The caller may call the function multiple times to read the whole + * object. + */ +typedef long (*ZT_DataStoreGetFunction)( + ZT_Node *, /* Node */ + void *, /* User ptr */ + void *, /* Thread ptr */ + const char *, + void *, + unsigned long, + unsigned long, + unsigned long *); + +/** + * Function to store an object in the data store + * + * Parameters: (1) node, (2) user ptr, (3) object name, (4) object data, + * (5) object size, (6) secure? (bool). + * + * If secure is true, the file should be set readable and writable only + * to the user running ZeroTier One. What this means is platform-specific. + * + * Name semantics are the same as the get function. This must return zero on + * success. You can return any OS-specific error code on failure, as these + * may be visible in logs or error messages and might aid in debugging. + * + * If the data pointer is null, this must be interpreted as a delete + * operation. + */ +typedef int (*ZT_DataStorePutFunction)( + ZT_Node *, + void *, + void *, /* Thread ptr */ + const char *, + const void *, + unsigned long, + int); + +/** + * Function to send a ZeroTier packet out over the wire + * + * Parameters: + * (1) Node + * (2) User pointer + * (3) Local interface address + * (4) Remote address + * (5) Packet data + * (6) Packet length + * (7) Desired IP TTL or 0 to use default + * + * If there is only one local interface it is safe to ignore the local + * interface address. Otherwise if running with multiple interfaces, the + * correct local interface should be chosen by address unless NULL. If + * the ss_family field is zero (NULL address), a random or preferred + * default interface should be used. + * + * If TTL is nonzero, packets should have their IP TTL value set to this + * value if possible. If this is not possible it is acceptable to ignore + * this value and send anyway with normal or default TTL. + * + * The function must return zero on success and may return any error code + * on failure. Note that success does not (of course) guarantee packet + * delivery. It only means that the packet appears to have been sent. + */ +typedef int (*ZT_WirePacketSendFunction)( + ZT_Node *, /* Node */ + void *, /* User ptr */ + void *, /* Thread ptr */ + const struct sockaddr_storage *, /* Local address */ + const struct sockaddr_storage *, /* Remote address */ + const void *, /* Packet data */ + unsigned int, /* Packet length */ + unsigned int); /* TTL or 0 to use default */ + +/** + * Function to check whether a path should be used for ZeroTier traffic + * + * Paramters: + * (1) Node + * (2) User pointer + * (3) ZeroTier address or 0 for none/any + * (4) Local interface address + * (5) Remote address + * + * This function must return nonzero (true) if the path should be used. + * + * If no path check function is specified, ZeroTier will still exclude paths + * that overlap with ZeroTier-assigned and managed IP address blocks. But the + * use of a path check function is recommended to ensure that recursion does + * not occur in cases where addresses are assigned by the OS or managed by + * an out of band mechanism like DHCP. The path check function should examine + * all configured ZeroTier interfaces and check to ensure that the supplied + * addresses will not result in ZeroTier traffic being sent over a ZeroTier + * interface (recursion). + * + * Obviously this is not required in configurations where this can't happen, + * such as network containers or embedded. + */ +typedef int (*ZT_PathCheckFunction)( + ZT_Node *, /* Node */ + void *, /* User ptr */ + void *, /* Thread ptr */ + uint64_t, /* ZeroTier address */ + const struct sockaddr_storage *, /* Local address */ + const struct sockaddr_storage *); /* Remote address */ + +/** + * Function to get physical addresses for ZeroTier peers + * + * Parameters: + * (1) Node + * (2) User pointer + * (3) ZeroTier address (least significant 40 bits) + * (4) Desried address family or -1 for any + * (5) Buffer to fill with result + * + * If provided this function will be occasionally called to get physical + * addresses that might be tried to reach a ZeroTier address. It must + * return a nonzero (true) value if the result buffer has been filled + * with an address. + */ +typedef int (*ZT_PathLookupFunction)( + ZT_Node *, /* Node */ + void *, /* User ptr */ + void *, /* Thread ptr */ + uint64_t, /* ZeroTier address (40 bits) */ + int, /* Desired ss_family or -1 for any */ + struct sockaddr_storage *); /* Result buffer */ + +/****************************************************************************/ +/* C Node API */ +/****************************************************************************/ + +/** + * Structure for configuring ZeroTier core callback functions + */ +struct ZT_Node_Callbacks +{ + /** + * Struct version -- must currently be 0 + */ + long version; + + /** + * REQUIRED: Function to get objects from persistent storage + */ + ZT_DataStoreGetFunction dataStoreGetFunction; + + /** + * REQUIRED: Function to store objects in persistent storage + */ + ZT_DataStorePutFunction dataStorePutFunction; + + /** + * REQUIRED: Function to send packets over the physical wire + */ + ZT_WirePacketSendFunction wirePacketSendFunction; + + /** + * REQUIRED: Function to inject frames into a virtual network's TAP + */ + ZT_VirtualNetworkFrameFunction virtualNetworkFrameFunction; + + /** + * REQUIRED: Function to be called when virtual networks are configured or changed + */ + ZT_VirtualNetworkConfigFunction virtualNetworkConfigFunction; + + /** + * REQUIRED: Function to be called to notify external code of important events + */ + ZT_EventCallback eventCallback; + + /** + * OPTIONAL: Function to check whether a given physical path should be used + */ + ZT_PathCheckFunction pathCheckFunction; + + /** + * OPTIONAL: Function to get hints to physical paths to ZeroTier addresses + */ + ZT_PathLookupFunction pathLookupFunction; +}; + +/** + * Create a new ZeroTier One node + * + * Note that this can take a few seconds the first time it's called, as it + * will generate an identity. + * + * TODO: should consolidate function pointers into versioned structure for + * better API stability. + * + * @param node Result: pointer is set to new node instance on success + * @param uptr User pointer to pass to functions/callbacks + * @param tptr Thread pointer to pass to functions/callbacks resulting from this call + * @param callbacks Callback function configuration + * @param now Current clock in milliseconds + * @return OK (0) or error code if a fatal error condition has occurred + */ +enum ZT_ResultCode ZT_Node_new(ZT_Node **node,void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,uint64_t now); + +/** + * Delete a node and free all resources it consumes + * + * If you are using multiple threads, all other threads must be shut down + * first. This can crash if processXXX() methods are in progress. + * + * @param node Node to delete + */ +void ZT_Node_delete(ZT_Node *node); + +/** + * Process a packet received from the physical wire + * + * @param node Node instance + * @param tptr Thread pointer to pass to functions/callbacks resulting from this call + * @param now Current clock in milliseconds + * @param localAddress Local address, or point to ZT_SOCKADDR_NULL if unspecified + * @param remoteAddress Origin of packet + * @param packetData Packet data + * @param packetLength Packet length + * @param nextBackgroundTaskDeadline Value/result: set to deadline for next call to processBackgroundTasks() + * @return OK (0) or error code if a fatal error condition has occurred + */ +enum ZT_ResultCode ZT_Node_processWirePacket( + ZT_Node *node, + void *tptr, + uint64_t now, + const struct sockaddr_storage *localAddress, + const struct sockaddr_storage *remoteAddress, + const void *packetData, + unsigned int packetLength, + volatile uint64_t *nextBackgroundTaskDeadline); + +/** + * Process a frame from a virtual network port (tap) + * + * @param node Node instance + * @param tptr Thread pointer to pass to functions/callbacks resulting from this call + * @param now Current clock in milliseconds + * @param nwid ZeroTier 64-bit virtual network ID + * @param sourceMac Source MAC address (least significant 48 bits) + * @param destMac Destination MAC address (least significant 48 bits) + * @param etherType 16-bit Ethernet frame type + * @param vlanId 10-bit VLAN ID or 0 if none + * @param frameData Frame payload data + * @param frameLength Frame payload length + * @param nextBackgroundTaskDeadline Value/result: set to deadline for next call to processBackgroundTasks() + * @return OK (0) or error code if a fatal error condition has occurred + */ +enum ZT_ResultCode ZT_Node_processVirtualNetworkFrame( + ZT_Node *node, + void *tptr, + uint64_t now, + uint64_t nwid, + uint64_t sourceMac, + uint64_t destMac, + unsigned int etherType, + unsigned int vlanId, + const void *frameData, + unsigned int frameLength, + volatile uint64_t *nextBackgroundTaskDeadline); + +/** + * Perform periodic background operations + * + * @param node Node instance + * @param tptr Thread pointer to pass to functions/callbacks resulting from this call + * @param now Current clock in milliseconds + * @param nextBackgroundTaskDeadline Value/result: set to deadline for next call to processBackgroundTasks() + * @return OK (0) or error code if a fatal error condition has occurred + */ +enum ZT_ResultCode ZT_Node_processBackgroundTasks(ZT_Node *node,void *tptr,uint64_t now,volatile uint64_t *nextBackgroundTaskDeadline); + +/** + * Join a network + * + * This may generate calls to the port config callback before it returns, + * or these may be deffered if a netconf is not available yet. + * + * If we are already a member of the network, nothing is done and OK is + * returned. + * + * @param node Node instance + * @param nwid 64-bit ZeroTier network ID + * @param uptr An arbitrary pointer to associate with this network (default: NULL) + * @return OK (0) or error code if a fatal error condition has occurred + */ +enum ZT_ResultCode ZT_Node_join(ZT_Node *node,uint64_t nwid,void *uptr,void *tptr); + +/** + * Leave a network + * + * If a port has been configured for this network this will generate a call + * to the port config callback with a NULL second parameter to indicate that + * the port is now deleted. + * + * The uptr parameter is optional and is NULL by default. If it is not NULL, + * the pointer it points to is set to this network's uptr on success. + * + * @param node Node instance + * @param nwid 64-bit network ID + * @param uptr Target pointer is set to uptr (if not NULL) + * @return OK (0) or error code if a fatal error condition has occurred + */ +enum ZT_ResultCode ZT_Node_leave(ZT_Node *node,uint64_t nwid,void **uptr,void *tptr); + +/** + * Subscribe to an Ethernet multicast group + * + * ADI stands for additional distinguishing information. This defaults to zero + * and is rarely used. Right now its only use is to enable IPv4 ARP to scale, + * and this must be done. + * + * For IPv4 ARP, the implementation must subscribe to 0xffffffffffff (the + * broadcast address) but with an ADI equal to each IPv4 address in host + * byte order. This converts ARP from a non-scalable broadcast protocol to + * a scalable multicast protocol with perfect address specificity. + * + * If this is not done, ARP will not work reliably. + * + * Multiple calls to subscribe to the same multicast address will have no + * effect. It is perfectly safe to do this. + * + * This does not generate an update call to networkConfigCallback(). + * + * @param node Node instance + * @param tptr Thread pointer to pass to functions/callbacks resulting from this call + * @param nwid 64-bit network ID + * @param multicastGroup Ethernet multicast or broadcast MAC (least significant 48 bits) + * @param multicastAdi Multicast ADI (least significant 32 bits only, use 0 if not needed) + * @return OK (0) or error code if a fatal error condition has occurred + */ +enum ZT_ResultCode ZT_Node_multicastSubscribe(ZT_Node *node,void *tptr,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi); + +/** + * Unsubscribe from an Ethernet multicast group (or all groups) + * + * If multicastGroup is zero (0), this will unsubscribe from all groups. If + * you are not subscribed to a group this has no effect. + * + * This does not generate an update call to networkConfigCallback(). + * + * @param node Node instance + * @param nwid 64-bit network ID + * @param multicastGroup Ethernet multicast or broadcast MAC (least significant 48 bits) + * @param multicastAdi Multicast ADI (least significant 32 bits only, use 0 if not needed) + * @return OK (0) or error code if a fatal error condition has occurred + */ +enum ZT_ResultCode ZT_Node_multicastUnsubscribe(ZT_Node *node,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi); + +/** + * Add or update a moon + * + * Moons are persisted in the data store in moons.d/, so this can persist + * across invocations if the contents of moon.d are scanned and orbit is + * called for each on startup. + * + * @param node Node instance + * @param tptr Thread pointer to pass to functions/callbacks resulting from this call + * @param moonWorldId Moon's world ID + * @param moonSeed If non-zero, the ZeroTier address of any member of the moon to query for moon definition + * @param len Length of moonWorld in bytes + * @return Error if moon was invalid or failed to be added + */ +enum ZT_ResultCode ZT_Node_orbit(ZT_Node *node,void *tptr,uint64_t moonWorldId,uint64_t moonSeed); + +/** + * Remove a moon (does nothing if not present) + * + * @param node Node instance + * @param tptr Thread pointer to pass to functions/callbacks resulting from this call + * @param moonWorldId World ID of moon to remove + * @return Error if anything bad happened + */ +enum ZT_ResultCode ZT_Node_deorbit(ZT_Node *node,void *tptr,uint64_t moonWorldId); + +/** + * Get this node's 40-bit ZeroTier address + * + * @param node Node instance + * @return ZeroTier address (least significant 40 bits of 64-bit int) + */ +uint64_t ZT_Node_address(ZT_Node *node); + +/** + * Get the status of this node + * + * @param node Node instance + * @param status Buffer to fill with current node status + */ +void ZT_Node_status(ZT_Node *node,ZT_NodeStatus *status); + +/** + * Get a list of known peer nodes + * + * The pointer returned here must be freed with freeQueryResult() + * when you are done with it. + * + * @param node Node instance + * @return List of known peers or NULL on failure + */ +ZT_PeerList *ZT_Node_peers(ZT_Node *node); + +/** + * Get the status of a virtual network + * + * The pointer returned here must be freed with freeQueryResult() + * when you are done with it. + * + * @param node Node instance + * @param nwid 64-bit network ID + * @return Network configuration or NULL if we are not a member of this network + */ +ZT_VirtualNetworkConfig *ZT_Node_networkConfig(ZT_Node *node,uint64_t nwid); + +/** + * Enumerate and get status of all networks + * + * @param node Node instance + * @return List of networks or NULL on failure + */ +ZT_VirtualNetworkList *ZT_Node_networks(ZT_Node *node); + +/** + * Free a query result buffer + * + * Use this to free the return values of listNetworks(), listPeers(), etc. + * + * @param node Node instance + * @param qr Query result buffer + */ +void ZT_Node_freeQueryResult(ZT_Node *node,void *qr); + +/** + * Add a local interface address + * + * This is used to make ZeroTier aware of those local interface addresses + * that you wish to use for ZeroTier communication. This is optional, and if + * it is not used ZeroTier will rely upon upstream peers (and roots) to + * perform empirical address discovery and NAT traversal. But the use of this + * method is recommended as it improves peer discovery when both peers are + * on the same LAN. + * + * It is the responsibility of the caller to take care that these are never + * ZeroTier interface addresses, whether these are assigned by ZeroTier or + * are otherwise assigned to an interface managed by this ZeroTier instance. + * This can cause recursion or other undesirable behavior. + * + * This returns a boolean indicating whether or not the address was + * accepted. ZeroTier will only communicate over certain address types + * and (for IP) address classes. + * + * @param addr Local interface address + * @return Boolean: non-zero if address was accepted and added + */ +int ZT_Node_addLocalInterfaceAddress(ZT_Node *node,const struct sockaddr_storage *addr); + +/** + * Clear local interface addresses + */ +void ZT_Node_clearLocalInterfaceAddresses(ZT_Node *node); + +/** + * Send a VERB_USER_MESSAGE to another ZeroTier node + * + * There is no delivery guarantee here. Failure can occur if the message is + * too large or if dest is not a valid ZeroTier address. + * + * @param node Node instance + * @param tptr Thread pointer to pass to functions/callbacks resulting from this call + * @param dest Destination ZeroTier address + * @param typeId VERB_USER_MESSAGE type ID + * @param data Payload data to attach to user message + * @param len Length of data in bytes + * @return Boolean: non-zero on success, zero on failure + */ +int ZT_Node_sendUserMessage(ZT_Node *node,void *tptr,uint64_t dest,uint64_t typeId,const void *data,unsigned int len); + +/** + * Set a network configuration master instance for this node + * + * Normal nodes should not need to use this. This is for nodes with + * special compiled-in support for acting as network configuration + * masters / controllers. + * + * The supplied instance must be a C++ object that inherits from the + * NetworkConfigMaster base class in node/. No type checking is performed, + * so a pointer to anything else will result in a crash. + * + * @param node ZertTier One node + * @param networkConfigMasterInstance Instance of NetworkConfigMaster C++ class or NULL to disable + * @return OK (0) or error code if a fatal error condition has occurred + */ +void ZT_Node_setNetconfMaster(ZT_Node *node,void *networkConfigMasterInstance); + +/** + * Initiate a VL1 circuit test + * + * This sends an initial VERB_CIRCUIT_TEST and reports results back to the + * supplied callback until circuitTestEnd() is called. The supplied + * ZT_CircuitTest structure should be initially zeroed and then filled + * in with settings and hops. + * + * It is the caller's responsibility to call circuitTestEnd() and then + * to dispose of the test structure. Otherwise this node will listen + * for results forever. + * + * @param node Node instance + * @param tptr Thread pointer to pass to functions/callbacks resulting from this call + * @param test Test configuration + * @param reportCallback Function to call each time a report is received + * @return OK or error if, for example, test is too big for a packet or support isn't compiled in + */ +enum ZT_ResultCode ZT_Node_circuitTestBegin(ZT_Node *node,void *tptr,ZT_CircuitTest *test,void (*reportCallback)(ZT_Node *, ZT_CircuitTest *,const ZT_CircuitTestReport *)); + +/** + * Stop listening for results to a given circuit test + * + * This does not free the 'test' structure. The caller may do that + * after calling this method to unregister it. + * + * Any reports that are received for a given test ID after it is + * terminated are ignored. + * + * @param node Node instance + * @param test Test configuration to unregister + */ +void ZT_Node_circuitTestEnd(ZT_Node *node,ZT_CircuitTest *test); + +/** + * Initialize cluster operation + * + * This initializes the internal structures and state for cluster operation. + * It takes two function pointers. The first is to a function that can be + * used to send data to cluster peers (mechanism is not defined by Node), + * and the second is to a function that can be used to get the location of + * a physical address in X,Y,Z coordinate space (e.g. as cartesian coordinates + * projected from the center of the Earth). + * + * Send function takes an arbitrary pointer followed by the cluster member ID + * to send data to, a pointer to the data, and the length of the data. The + * maximum message length is ZT_CLUSTER_MAX_MESSAGE_LENGTH (65535). Messages + * must be delivered whole and may be dropped or transposed, though high + * failure rates are undesirable and can cause problems. Validity checking or + * CRC is also not required since the Node validates the authenticity of + * cluster messages using cryptogrphic methods and will silently drop invalid + * messages. + * + * Address to location function is optional and if NULL geo-handoff is not + * enabled (in this case x, y, and z in clusterInit are also unused). It + * takes an arbitrary pointer followed by a physical address and three result + * parameters for x, y, and z. It returns zero on failure or nonzero if these + * three coordinates have been set. Coordinate space is arbitrary and can be + * e.g. coordinates on Earth relative to Earth's center. These can be obtained + * from latitutde and longitude with versions of the Haversine formula. + * + * See: http://stackoverflow.com/questions/1185408/converting-from-longitude-latitude-to-cartesian-coordinates + * + * Neither the send nor the address to location function should block. If the + * address to location function does not have a location for an address, it + * should return zero and then look up the address for future use since it + * will be called again in (typically) 1-3 minutes. + * + * Note that both functions can be called from any thread from which the + * various Node functions are called, and so must be thread safe if multiple + * threads are being used. + * + * @param node Node instance + * @param myId My cluster member ID (less than or equal to ZT_CLUSTER_MAX_MEMBERS) + * @param zeroTierPhysicalEndpoints Preferred physical address(es) for ZeroTier clients to contact this cluster member (for peer redirect) + * @param numZeroTierPhysicalEndpoints Number of physical endpoints in zeroTierPhysicalEndpoints[] (max allowed: 255) + * @param x My cluster member's X location + * @param y My cluster member's Y location + * @param z My cluster member's Z location + * @param sendFunction Function to be called to send data to other cluster members + * @param sendFunctionArg First argument to sendFunction() + * @param addressToLocationFunction Function to be called to get the location of a physical address or NULL to disable geo-handoff + * @param addressToLocationFunctionArg First argument to addressToLocationFunction() + * @return OK or UNSUPPORTED_OPERATION if this Node was not built with cluster support + */ +enum ZT_ResultCode ZT_Node_clusterInit( + ZT_Node *node, + unsigned int myId, + const struct sockaddr_storage *zeroTierPhysicalEndpoints, + unsigned int numZeroTierPhysicalEndpoints, + int x, + int y, + int z, + void (*sendFunction)(void *,unsigned int,const void *,unsigned int), + void *sendFunctionArg, + int (*addressToLocationFunction)(void *,const struct sockaddr_storage *,int *,int *,int *), + void *addressToLocationFunctionArg); + +/** + * Add a member to this cluster + * + * Calling this without having called clusterInit() will do nothing. + * + * @param node Node instance + * @param memberId Member ID (must be less than or equal to ZT_CLUSTER_MAX_MEMBERS) + * @return OK or error if clustering is disabled, ID invalid, etc. + */ +enum ZT_ResultCode ZT_Node_clusterAddMember(ZT_Node *node,unsigned int memberId); + +/** + * Remove a member from this cluster + * + * Calling this without having called clusterInit() will do nothing. + * + * @param node Node instance + * @param memberId Member ID to remove (nothing happens if not present) + */ +void ZT_Node_clusterRemoveMember(ZT_Node *node,unsigned int memberId); + +/** + * Handle an incoming cluster state message + * + * The message itself contains cluster member IDs, and invalid or badly + * addressed messages will be silently discarded. + * + * Calling this without having called clusterInit() will do nothing. + * + * @param node Node instance + * @param msg Cluster message + * @param len Length of cluster message + */ +void ZT_Node_clusterHandleIncomingMessage(ZT_Node *node,const void *msg,unsigned int len); + +/** + * Get the current status of the cluster from this node's point of view + * + * Calling this without clusterInit() or without cluster support will just + * zero out the structure and show a cluster size of zero. + * + * @param node Node instance + * @param cs Cluster status structure to fill with data + */ +void ZT_Node_clusterStatus(ZT_Node *node,ZT_ClusterStatus *cs); + +/** + * Set trusted paths + * + * A trusted path is a physical network (network/bits) over which both + * encryption and authentication can be skipped to improve performance. + * Each trusted path must have a non-zero unique ID that is the same across + * all participating nodes. + * + * We don't recommend using trusted paths at all unless you really *need* + * near-bare-metal performance. Even on a LAN authentication and encryption + * are never a bad thing, and anything that introduces an "escape hatch" + * for encryption should be treated with the utmost care. + * + * Calling with NULL pointers for networks and ids and a count of zero clears + * all trusted paths. + * + * @param node Node instance + * @param networks Array of [count] networks + * @param ids Array of [count] corresponding non-zero path IDs (zero path IDs are ignored) + * @param count Number of trusted paths-- values greater than ZT_MAX_TRUSTED_PATHS are clipped + */ +void ZT_Node_setTrustedPaths(ZT_Node *node,const struct sockaddr_storage *networks,const uint64_t *ids,unsigned int count); + +/** + * Get ZeroTier One version + * + * @param major Result: major version + * @param minor Result: minor version + * @param revision Result: revision + */ +void ZT_version(int *major,int *minor,int *revision); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/java/CMakeLists.txt b/java/CMakeLists.txt new file mode 100644 index 0000000..008b747 --- /dev/null +++ b/java/CMakeLists.txt @@ -0,0 +1,91 @@ +cmake_minimum_required(VERSION 3.2) + +project(ZeroTierOneJNI) + +find_package(Java COMPONENTS Development) +message("JAVA_HOME: $ENV{JAVA_HOME}") + +if(WIN32) +set(Java_INCLUDE_DIRS $ENV{JAVA_HOME}/include) +endif() + +if(APPLE) +set(Java_INCLUDE_DIRS "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/System/Library/Frameworks/JavaVM.framework/Headers") +endif() + +message("Java Include Dirs: ${Java_INCLUDE_DIRS}") + +if(WIN32) + add_definitions(-DNOMINMAX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc /W3 /MP") +endif() + +if(APPLE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -arch i386 -arch x86_64 -Wall -O3 -flto -fPIE -fvectorize -fstack-protector -mmacosx-version-min=10.7 -Wno-unused-private-field") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} -fno-rtti") +endif() + +set(src_files + ../ext/lz4/lz4.c + ../ext/json-parser/json.c + ../ext/http-parser/http_parser.c + ../node/C25519.cpp + ../node/CertificateOfMembership.cpp + ../node/Defaults.cpp + ../node/Dictionary.cpp + ../node/Identity.cpp + ../node/IncomingPacket.cpp + ../node/InetAddress.cpp + ../node/Multicaster.cpp + ../node/Network.cpp + ../node/NetworkConfig.cpp + ../node/Node.cpp + ../node/OutboundMulticast.cpp + ../node/Packet.cpp + ../node/Peer.cpp + ../node/Poly1305.cpp + ../node/Salsa20.cpp + ../node/SelfAwareness.cpp + ../node/SHA512.cpp + ../node/Switch.cpp + ../node/Topology.cpp + ../node/Utils.cpp + ../osdep/Http.cpp + ../osdep/OSUtils.cpp + jni/com_zerotierone_sdk_Node.cpp + jni/ZT_jniutils.cpp + jni/ZT_jnicache.cpp + ) + +set(include_dirs + ${CMAKE_CURRENT_SOURCE_DIR}/../include/ + ${CMAKE_CURRENT_SOURCE_DIR}/../node/ + ${Java_INCLUDE_DIRS}) + +if(WIN32) + set(include_dirs + ${include_dirs} + ${Java_INCLUDE_DIRS}/win32) +endif() + +include_directories( + ${include_dirs} + ) + +add_library(${PROJECT_NAME} SHARED ${src_files}) + +if(APPLE) + set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX ".jnilib") +endif() + +set(link_libs ) + +if(WIN32) + set(link_libs + wsock32 + ws2_32 + + ) +endif() + +target_link_libraries(${PROJECT_NAME} ${link_libs}) \ No newline at end of file diff --git a/java/README.md b/java/README.md new file mode 100644 index 0000000..2650ec3 --- /dev/null +++ b/java/README.md @@ -0,0 +1,17 @@ +ZeroTier One SDK - Android JNI Wrapper +===== + + +Building +----- + +Reqires: + +* JDK +* ANT +* Android NDK + +Required Environment Variables: + +* NDK\_BUILD\_LOC - Path do the ndk-build script in the Android NDK +* ANDROID\_PLATFORM - path to the directory android.jar lives (on Windows: C:\Users\\AppData\Local\Android\sdk\platforms\android-21) diff --git a/java/build.xml b/java/build.xml new file mode 100644 index 0000000..4604ad6 --- /dev/null +++ b/java/build.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/jni/Android.mk b/java/jni/Android.mk new file mode 100644 index 0000000..ebd8937 --- /dev/null +++ b/java/jni/Android.mk @@ -0,0 +1,46 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := ZeroTierOneJNI +LOCAL_C_INCLUDES := $(ZT1)/include +LOCAL_C_INCLUDES += $(ZT1)/node +LOCAL_LDLIBS := -llog -latomic +# LOCAL_CFLAGS := -g + +# ZeroTierOne SDK source files +LOCAL_SRC_FILES := \ + $(ZT1)/node/C25519.cpp \ + $(ZT1)/node/Capability.cpp \ + $(ZT1)/node/CertificateOfMembership.cpp \ + $(ZT1)/node/CertificateOfOwnership.cpp \ + $(ZT1)/node/Identity.cpp \ + $(ZT1)/node/IncomingPacket.cpp \ + $(ZT1)/node/InetAddress.cpp \ + $(ZT1)/node/Membership.cpp \ + $(ZT1)/node/Multicaster.cpp \ + $(ZT1)/node/Network.cpp \ + $(ZT1)/node/NetworkConfig.cpp \ + $(ZT1)/node/Node.cpp \ + $(ZT1)/node/OutboundMulticast.cpp \ + $(ZT1)/node/Packet.cpp \ + $(ZT1)/node/Path.cpp \ + $(ZT1)/node/Peer.cpp \ + $(ZT1)/node/Poly1305.cpp \ + $(ZT1)/node/Revocation.cpp \ + $(ZT1)/node/Salsa20.cpp \ + $(ZT1)/node/SelfAwareness.cpp \ + $(ZT1)/node/SHA512.cpp \ + $(ZT1)/node/Switch.cpp \ + $(ZT1)/node/Tag.cpp \ + $(ZT1)/node/Topology.cpp \ + $(ZT1)/node/Utils.cpp + + +# JNI Files +LOCAL_SRC_FILES += \ + com_zerotierone_sdk_Node.cpp \ + ZT_jniutils.cpp \ + ZT_jnilookup.cpp + +include $(BUILD_SHARED_LIBRARY) \ No newline at end of file diff --git a/java/jni/Application.mk b/java/jni/Application.mk new file mode 100644 index 0000000..19891cc --- /dev/null +++ b/java/jni/Application.mk @@ -0,0 +1,5 @@ +# NDK_TOOLCHAIN_VERSION := clang3.5 +APP_STL := c++_static +APP_CPPFLAGS := -O3 -Wall -fstack-protector -fexceptions -fno-strict-aliasing -Wno-deprecated-register -DZT_NO_TYPE_PUNNING=1 +APP_PLATFORM := android-14 +APP_ABI := all diff --git a/java/jni/ZT_jnilookup.cpp b/java/jni/ZT_jnilookup.cpp new file mode 100644 index 0000000..be52a36 --- /dev/null +++ b/java/jni/ZT_jnilookup.cpp @@ -0,0 +1,158 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include "ZT_jnilookup.h" +#include "ZT_jniutils.h" + +JniLookup::JniLookup() + : m_jvm(NULL) +{ + LOGV("JNI Cache Created"); +} + +JniLookup::JniLookup(JavaVM *jvm) + : m_jvm(jvm) +{ + LOGV("JNI Cache Created"); +} + +JniLookup::~JniLookup() +{ + LOGV("JNI Cache Destroyed"); +} + + +void JniLookup::setJavaVM(JavaVM *jvm) +{ + LOGV("Assigned JVM to object"); + m_jvm = jvm; +} + + +jclass JniLookup::findClass(const std::string &name) +{ + if(!m_jvm) + return NULL; + + // get the class from the JVM + JNIEnv *env = NULL; + if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) + { + LOGE("Error retreiving JNI Environment"); + return NULL; + } + + jclass cls = env->FindClass(name.c_str()); + if(env->ExceptionCheck()) + { + LOGE("Error finding class: %s", name.c_str()); + return NULL; + } + + return cls; +} + + +jmethodID JniLookup::findMethod(jclass cls, const std::string &methodName, const std::string &methodSig) +{ + if(!m_jvm) + return NULL; + + JNIEnv *env = NULL; + if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) + { + return NULL; + } + + jmethodID mid = env->GetMethodID(cls, methodName.c_str(), methodSig.c_str()); + if(env->ExceptionCheck()) + { + return NULL; + } + + return mid; +} + +jmethodID JniLookup::findStaticMethod(jclass cls, const std::string &methodName, const std::string &methodSig) +{ + if(!m_jvm) + return NULL; + + JNIEnv *env = NULL; + if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) + { + return NULL; + } + + jmethodID mid = env->GetStaticMethodID(cls, methodName.c_str(), methodSig.c_str()); + if(env->ExceptionCheck()) + { + return NULL; + } + + return mid; +} + +jfieldID JniLookup::findField(jclass cls, const std::string &fieldName, const std::string &typeStr) +{ + if(!m_jvm) + return NULL; + + JNIEnv *env = NULL; + if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) + { + return NULL; + } + + jfieldID fid = env->GetFieldID(cls, fieldName.c_str(), typeStr.c_str()); + if(env->ExceptionCheck()) + { + return NULL; + } + + return fid; +} + +jfieldID JniLookup::findStaticField(jclass cls, const std::string &fieldName, const std::string &typeStr) +{ + if(!m_jvm) + return NULL; + + JNIEnv *env = NULL; + if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) + { + return NULL; + } + + jfieldID fid = env->GetStaticFieldID(cls, fieldName.c_str(), typeStr.c_str()); + if(env->ExceptionCheck()) + { + return NULL; + } + + return fid; +} \ No newline at end of file diff --git a/java/jni/ZT_jnilookup.h b/java/jni/ZT_jnilookup.h new file mode 100644 index 0000000..f5bd97d --- /dev/null +++ b/java/jni/ZT_jnilookup.h @@ -0,0 +1,54 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_JNILOOKUP_H_ +#define ZT_JNILOOKUP_H_ + +#include +#include +#include + + + +class JniLookup { +public: + JniLookup(); + JniLookup(JavaVM *jvm); + ~JniLookup(); + + void setJavaVM(JavaVM *jvm); + + jclass findClass(const std::string &name); + jmethodID findMethod(jclass cls, const std::string &methodName, const std::string &methodSig); + jmethodID findStaticMethod(jclass cls, const std::string &methodName, const std::string &methodSig); + jfieldID findField(jclass cls, const std::string &fieldName, const std::string &typeStr); + jfieldID findStaticField(jclass cls, const std::string &fieldName, const std::string &typeStr); +private: + JavaVM *m_jvm; +}; + +#endif \ No newline at end of file diff --git a/java/jni/ZT_jniutils.cpp b/java/jni/ZT_jniutils.cpp new file mode 100644 index 0000000..7bdc761 --- /dev/null +++ b/java/jni/ZT_jniutils.cpp @@ -0,0 +1,941 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include "ZT_jniutils.h" +#include "ZT_jnilookup.h" +#include +#include + +extern JniLookup lookup; + +#ifdef __cplusplus +extern "C" { +#endif + +jobject createResultObject(JNIEnv *env, ZT_ResultCode code) +{ + jclass resultClass = NULL; + + jobject resultObject = NULL; + + resultClass = lookup.findClass("com/zerotier/sdk/ResultCode"); + if(resultClass == NULL) + { + LOGE("Couldnt find ResultCode class"); + return NULL; // exception thrown + } + + std::string fieldName; + switch(code) + { + case ZT_RESULT_OK: + LOGV("ZT_RESULT_OK"); + fieldName = "RESULT_OK"; + break; + case ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY: + LOGV("ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY"); + fieldName = "RESULT_FATAL_ERROR_OUT_OF_MEMORY"; + break; + case ZT_RESULT_FATAL_ERROR_DATA_STORE_FAILED: + LOGV("RESULT_FATAL_ERROR_DATA_STORE_FAILED"); + fieldName = "RESULT_FATAL_ERROR_DATA_STORE_FAILED"; + break; + case ZT_RESULT_ERROR_NETWORK_NOT_FOUND: + LOGV("RESULT_FATAL_ERROR_DATA_STORE_FAILED"); + fieldName = "RESULT_ERROR_NETWORK_NOT_FOUND"; + break; + case ZT_RESULT_FATAL_ERROR_INTERNAL: + default: + LOGV("RESULT_FATAL_ERROR_DATA_STORE_FAILED"); + fieldName = "RESULT_FATAL_ERROR_INTERNAL"; + break; + } + + jfieldID enumField = lookup.findStaticField(resultClass, fieldName.c_str(), "Lcom/zerotier/sdk/ResultCode;"); + if(env->ExceptionCheck() || enumField == NULL) + { + LOGE("Error on FindStaticField"); + return NULL; + } + + resultObject = env->GetStaticObjectField(resultClass, enumField); + if(env->ExceptionCheck() || resultObject == NULL) + { + LOGE("Error on GetStaticObjectField"); + } + return resultObject; +} + + +jobject createVirtualNetworkStatus(JNIEnv *env, ZT_VirtualNetworkStatus status) +{ + jobject statusObject = NULL; + + jclass statusClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkStatus"); + if(statusClass == NULL) + { + return NULL; // exception thrown + } + + std::string fieldName; + switch(status) + { + case ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION: + fieldName = "NETWORK_STATUS_REQUESTING_CONFIGURATION"; + break; + case ZT_NETWORK_STATUS_OK: + fieldName = "NETWORK_STATUS_OK"; + break; + case ZT_NETWORK_STATUS_ACCESS_DENIED: + fieldName = "NETWORK_STATUS_ACCESS_DENIED"; + break; + case ZT_NETWORK_STATUS_NOT_FOUND: + fieldName = "NETWORK_STATUS_NOT_FOUND"; + break; + case ZT_NETWORK_STATUS_PORT_ERROR: + fieldName = "NETWORK_STATUS_PORT_ERROR"; + break; + case ZT_NETWORK_STATUS_CLIENT_TOO_OLD: + fieldName = "NETWORK_STATUS_CLIENT_TOO_OLD"; + break; + } + + jfieldID enumField = lookup.findStaticField(statusClass, fieldName.c_str(), "Lcom/zerotier/sdk/VirtualNetworkStatus;"); + + statusObject = env->GetStaticObjectField(statusClass, enumField); + + return statusObject; +} + +jobject createEvent(JNIEnv *env, ZT_Event event) +{ + jclass eventClass = NULL; + jobject eventObject = NULL; + + eventClass = lookup.findClass("com/zerotier/sdk/Event"); + if(eventClass == NULL) + { + return NULL; + } + + std::string fieldName; + switch(event) + { + case ZT_EVENT_UP: + fieldName = "EVENT_UP"; + break; + case ZT_EVENT_OFFLINE: + fieldName = "EVENT_OFFLINE"; + break; + case ZT_EVENT_ONLINE: + fieldName = "EVENT_ONLINE"; + break; + case ZT_EVENT_DOWN: + fieldName = "EVENT_DOWN"; + break; + case ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION: + fieldName = "EVENT_FATAL_ERROR_IDENTITY_COLLISION"; + break; + case ZT_EVENT_TRACE: + fieldName = "EVENT_TRACE"; + break; + case ZT_EVENT_USER_MESSAGE: + break; + } + + jfieldID enumField = lookup.findStaticField(eventClass, fieldName.c_str(), "Lcom/zerotier/sdk/Event;"); + + eventObject = env->GetStaticObjectField(eventClass, enumField); + + return eventObject; +} + +jobject createPeerRole(JNIEnv *env, ZT_PeerRole role) +{ + jclass peerRoleClass = NULL; + jobject peerRoleObject = NULL; + + peerRoleClass = lookup.findClass("com/zerotier/sdk/PeerRole"); + if(peerRoleClass == NULL) + { + return NULL; + } + + std::string fieldName; + switch(role) + { + case ZT_PEER_ROLE_LEAF: + fieldName = "PEER_ROLE_LEAF"; + break; + case ZT_PEER_ROLE_MOON: + fieldName = "PEER_ROLE_MOON"; + break; + case ZT_PEER_ROLE_PLANET: + fieldName = "PEER_ROLE_PLANET"; + break; + } + + jfieldID enumField = lookup.findStaticField(peerRoleClass, fieldName.c_str(), "Lcom/zerotier/sdk/PeerRole;"); + + peerRoleObject = env->GetStaticObjectField(peerRoleClass, enumField); + + return peerRoleObject; +} + +jobject createVirtualNetworkType(JNIEnv *env, ZT_VirtualNetworkType type) +{ + jclass vntypeClass = NULL; + jobject vntypeObject = NULL; + + vntypeClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkType"); + if(env->ExceptionCheck() || vntypeClass == NULL) + { + return NULL; + } + + std::string fieldName; + switch(type) + { + case ZT_NETWORK_TYPE_PRIVATE: + fieldName = "NETWORK_TYPE_PRIVATE"; + break; + case ZT_NETWORK_TYPE_PUBLIC: + fieldName = "NETWORK_TYPE_PUBLIC"; + break; + } + + jfieldID enumField = lookup.findStaticField(vntypeClass, fieldName.c_str(), "Lcom/zerotier/sdk/VirtualNetworkType;"); + vntypeObject = env->GetStaticObjectField(vntypeClass, enumField); + return vntypeObject; +} + +jobject createVirtualNetworkConfigOperation(JNIEnv *env, ZT_VirtualNetworkConfigOperation op) +{ + jclass vnetConfigOpClass = NULL; + jobject vnetConfigOpObject = NULL; + + vnetConfigOpClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkConfigOperation"); + if(env->ExceptionCheck() || vnetConfigOpClass == NULL) + { + return NULL; + } + + std::string fieldName; + switch(op) + { + case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP: + fieldName = "VIRTUAL_NETWORK_CONFIG_OPERATION_UP"; + break; + case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE: + fieldName = "VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE"; + break; + case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN: + fieldName = "VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN"; + break; + case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY: + fieldName = "VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY"; + break; + } + + jfieldID enumField = lookup.findStaticField(vnetConfigOpClass, fieldName.c_str(), "Lcom/zerotier/sdk/VirtualNetworkConfigOperation;"); + vnetConfigOpObject = env->GetStaticObjectField(vnetConfigOpClass, enumField); + return vnetConfigOpObject; +} + +jobject newInetAddress(JNIEnv *env, const sockaddr_storage &addr) +{ + LOGV("newInetAddress"); + jclass inetAddressClass = NULL; + jmethodID inetAddress_getByAddress = NULL; + + inetAddressClass = lookup.findClass("java/net/InetAddress"); + if(env->ExceptionCheck() || inetAddressClass == NULL) + { + LOGE("Error finding InetAddress class"); + return NULL; + } + + inetAddress_getByAddress = lookup.findStaticMethod( + inetAddressClass, "getByAddress", "([B)Ljava/net/InetAddress;"); + if(env->ExceptionCheck() || inetAddress_getByAddress == NULL) + { + LOGE("Erorr finding getByAddress() static method"); + return NULL; + } + + jobject inetAddressObj = NULL; + switch(addr.ss_family) + { + case AF_INET6: + { + sockaddr_in6 *ipv6 = (sockaddr_in6*)&addr; + jbyteArray buff = env->NewByteArray(16); + if(buff == NULL) + { + LOGE("Error creating IPV6 byte array"); + return NULL; + } + + env->SetByteArrayRegion(buff, 0, 16, (jbyte*)ipv6->sin6_addr.s6_addr); + inetAddressObj = env->CallStaticObjectMethod( + inetAddressClass, inetAddress_getByAddress, buff); + } + break; + case AF_INET: + { + sockaddr_in *ipv4 = (sockaddr_in*)&addr; + jbyteArray buff = env->NewByteArray(4); + if(buff == NULL) + { + LOGE("Error creating IPV4 byte array"); + return NULL; + } + + env->SetByteArrayRegion(buff, 0, 4, (jbyte*)&ipv4->sin_addr); + inetAddressObj = env->CallStaticObjectMethod( + inetAddressClass, inetAddress_getByAddress, buff); + } + break; + } + if(env->ExceptionCheck() || inetAddressObj == NULL) { + LOGE("Error creating InetAddress object"); + return NULL; + } + + return inetAddressObj; +} + +jobject newInetSocketAddress(JNIEnv *env, const sockaddr_storage &addr) +{ + LOGV("newInetSocketAddress Called"); + jclass inetSocketAddressClass = NULL; + jmethodID inetSocketAddress_constructor = NULL; + + inetSocketAddressClass = lookup.findClass("java/net/InetSocketAddress"); + if(env->ExceptionCheck() || inetSocketAddressClass == NULL) + { + LOGE("Error finding InetSocketAddress Class"); + return NULL; + } + + jobject inetAddressObject = NULL; + + if(addr.ss_family != 0) + { + inetAddressObject = newInetAddress(env, addr); + + if(env->ExceptionCheck() || inetAddressObject == NULL) + { + LOGE("Error creating new inet address"); + return NULL; + } + } + else + { + return NULL; + } + + inetSocketAddress_constructor = lookup.findMethod( + inetSocketAddressClass, "", "(Ljava/net/InetAddress;I)V"); + if(env->ExceptionCheck() || inetSocketAddress_constructor == NULL) + { + LOGE("Error finding InetSocketAddress constructor"); + return NULL; + } + + int port = 0; + switch(addr.ss_family) + { + case AF_INET6: + { + LOGV("IPV6 Address"); + sockaddr_in6 *ipv6 = (sockaddr_in6*)&addr; + port = ntohs(ipv6->sin6_port); + LOGV("Port %d", port); + } + break; + case AF_INET: + { + LOGV("IPV4 Address"); + sockaddr_in *ipv4 = (sockaddr_in*)&addr; + port = ntohs(ipv4->sin_port); + LOGV("Port: %d", port); + } + break; + default: + { + break; + } + } + + + jobject inetSocketAddressObject = env->NewObject(inetSocketAddressClass, inetSocketAddress_constructor, inetAddressObject, port); + if(env->ExceptionCheck() || inetSocketAddressObject == NULL) { + LOGE("Error creating InetSocketAddress object"); + } + return inetSocketAddressObject; +} + +jobject newPeerPhysicalPath(JNIEnv *env, const ZT_PeerPhysicalPath &ppp) +{ + LOGV("newPeerPhysicalPath Called"); + jclass pppClass = NULL; + + jfieldID addressField = NULL; + jfieldID lastSendField = NULL; + jfieldID lastReceiveField = NULL; + jfieldID preferredField = NULL; + + jmethodID ppp_constructor = NULL; + + pppClass = lookup.findClass("com/zerotier/sdk/PeerPhysicalPath"); + if(env->ExceptionCheck() || pppClass == NULL) + { + LOGE("Error finding PeerPhysicalPath class"); + return NULL; + } + + addressField = lookup.findField(pppClass, "address", "Ljava/net/InetSocketAddress;"); + if(env->ExceptionCheck() || addressField == NULL) + { + LOGE("Error finding address field"); + return NULL; + } + + lastSendField = lookup.findField(pppClass, "lastSend", "J"); + if(env->ExceptionCheck() || lastSendField == NULL) + { + LOGE("Error finding lastSend field"); + return NULL; + } + + lastReceiveField = lookup.findField(pppClass, "lastReceive", "J"); + if(env->ExceptionCheck() || lastReceiveField == NULL) + { + LOGE("Error finding lastReceive field"); + return NULL; + } + + preferredField = lookup.findField(pppClass, "preferred", "Z"); + if(env->ExceptionCheck() || preferredField == NULL) + { + LOGE("Error finding preferred field"); + return NULL; + } + + ppp_constructor = lookup.findMethod(pppClass, "", "()V"); + if(env->ExceptionCheck() || ppp_constructor == NULL) + { + LOGE("Error finding PeerPhysicalPath constructor"); + return NULL; + } + + jobject pppObject = env->NewObject(pppClass, ppp_constructor); + if(env->ExceptionCheck() || pppObject == NULL) + { + LOGE("Error creating PPP object"); + return NULL; // out of memory + } + + jobject addressObject = newInetSocketAddress(env, ppp.address); + if(env->ExceptionCheck() || addressObject == NULL) { + LOGE("Error creating InetSocketAddress object"); + return NULL; + } + + env->SetObjectField(pppObject, addressField, addressObject); + env->SetLongField(pppObject, lastSendField, ppp.lastSend); + env->SetLongField(pppObject, lastReceiveField, ppp.lastReceive); + env->SetBooleanField(pppObject, preferredField, ppp.preferred); + + if(env->ExceptionCheck()) { + LOGE("Exception assigning fields to PeerPhysicalPath object"); + } + + return pppObject; +} + +jobject newPeer(JNIEnv *env, const ZT_Peer &peer) +{ + LOGV("newPeer called"); + + jclass peerClass = NULL; + + jfieldID addressField = NULL; + jfieldID versionMajorField = NULL; + jfieldID versionMinorField = NULL; + jfieldID versionRevField = NULL; + jfieldID latencyField = NULL; + jfieldID roleField = NULL; + jfieldID pathsField = NULL; + + jmethodID peer_constructor = NULL; + + peerClass = lookup.findClass("com/zerotier/sdk/Peer"); + if(env->ExceptionCheck() || peerClass == NULL) + { + LOGE("Error finding Peer class"); + return NULL; + } + + addressField = lookup.findField(peerClass, "address", "J"); + if(env->ExceptionCheck() || addressField == NULL) + { + LOGE("Error finding address field of Peer object"); + return NULL; + } + + versionMajorField = lookup.findField(peerClass, "versionMajor", "I"); + if(env->ExceptionCheck() || versionMajorField == NULL) + { + LOGE("Error finding versionMajor field of Peer object"); + return NULL; + } + + versionMinorField = lookup.findField(peerClass, "versionMinor", "I"); + if(env->ExceptionCheck() || versionMinorField == NULL) + { + LOGE("Error finding versionMinor field of Peer object"); + return NULL; + } + + versionRevField = lookup.findField(peerClass, "versionRev", "I"); + if(env->ExceptionCheck() || versionRevField == NULL) + { + LOGE("Error finding versionRev field of Peer object"); + return NULL; + } + + latencyField = lookup.findField(peerClass, "latency", "I"); + if(env->ExceptionCheck() || latencyField == NULL) + { + LOGE("Error finding latency field of Peer object"); + return NULL; + } + + roleField = lookup.findField(peerClass, "role", "Lcom/zerotier/sdk/PeerRole;"); + if(env->ExceptionCheck() || roleField == NULL) + { + LOGE("Error finding role field of Peer object"); + return NULL; + } + + pathsField = lookup.findField(peerClass, "paths", "[Lcom/zerotier/sdk/PeerPhysicalPath;"); + if(env->ExceptionCheck() || pathsField == NULL) + { + LOGE("Error finding paths field of Peer object"); + return NULL; + } + + peer_constructor = lookup.findMethod(peerClass, "", "()V"); + if(env->ExceptionCheck() || peer_constructor == NULL) + { + LOGE("Error finding Peer constructor"); + return NULL; + } + + jobject peerObject = env->NewObject(peerClass, peer_constructor); + if(env->ExceptionCheck() || peerObject == NULL) + { + LOGE("Error creating Peer object"); + return NULL; // out of memory + } + + env->SetLongField(peerObject, addressField, (jlong)peer.address); + env->SetIntField(peerObject, versionMajorField, peer.versionMajor); + env->SetIntField(peerObject, versionMinorField, peer.versionMinor); + env->SetIntField(peerObject, versionRevField, peer.versionRev); + env->SetIntField(peerObject, latencyField, peer.latency); + env->SetObjectField(peerObject, roleField, createPeerRole(env, peer.role)); + + jclass peerPhysicalPathClass = lookup.findClass("com/zerotier/sdk/PeerPhysicalPath"); + if(env->ExceptionCheck() || peerPhysicalPathClass == NULL) + { + LOGE("Error finding PeerPhysicalPath class"); + return NULL; + } + + jobjectArray arrayObject = env->NewObjectArray( + peer.pathCount, peerPhysicalPathClass, NULL); + if(env->ExceptionCheck() || arrayObject == NULL) + { + LOGE("Error creating PeerPhysicalPath[] array"); + return NULL; + } + + for(unsigned int i = 0; i < peer.pathCount; ++i) + { + jobject path = newPeerPhysicalPath(env, peer.paths[i]); + + env->SetObjectArrayElement(arrayObject, i, path); + if(env->ExceptionCheck()) { + LOGE("exception assigning PeerPhysicalPath to array"); + break; + } + } + + env->SetObjectField(peerObject, pathsField, arrayObject); + + return peerObject; +} + +jobject newNetworkConfig(JNIEnv *env, const ZT_VirtualNetworkConfig &vnetConfig) +{ + jclass vnetConfigClass = NULL; + jmethodID vnetConfig_constructor = NULL; + jfieldID nwidField = NULL; + jfieldID macField = NULL; + jfieldID nameField = NULL; + jfieldID statusField = NULL; + jfieldID typeField = NULL; + jfieldID mtuField = NULL; + jfieldID dhcpField = NULL; + jfieldID bridgeField = NULL; + jfieldID broadcastEnabledField = NULL; + jfieldID portErrorField = NULL; + jfieldID netconfRevisionField = NULL; + jfieldID assignedAddressesField = NULL; + jfieldID routesField = NULL; + + vnetConfigClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkConfig"); + if(vnetConfigClass == NULL) + { + LOGE("Couldn't find com.zerotier.sdk.VirtualNetworkConfig"); + return NULL; + } + + vnetConfig_constructor = lookup.findMethod( + vnetConfigClass, "", "()V"); + if(env->ExceptionCheck() || vnetConfig_constructor == NULL) + { + LOGE("Couldn't find VirtualNetworkConfig Constructor"); + return NULL; + } + + jobject vnetConfigObj = env->NewObject(vnetConfigClass, vnetConfig_constructor); + if(env->ExceptionCheck() || vnetConfigObj == NULL) + { + LOGE("Error creating new VirtualNetworkConfig object"); + return NULL; + } + + nwidField = lookup.findField(vnetConfigClass, "nwid", "J"); + if(env->ExceptionCheck() || nwidField == NULL) + { + LOGE("Error getting nwid field"); + return NULL; + } + + macField = lookup.findField(vnetConfigClass, "mac", "J"); + if(env->ExceptionCheck() || macField == NULL) + { + LOGE("Error getting mac field"); + return NULL; + } + + nameField = lookup.findField(vnetConfigClass, "name", "Ljava/lang/String;"); + if(env->ExceptionCheck() || nameField == NULL) + { + LOGE("Error getting name field"); + return NULL; + } + + statusField = lookup.findField(vnetConfigClass, "status", "Lcom/zerotier/sdk/VirtualNetworkStatus;"); + if(env->ExceptionCheck() || statusField == NULL) + { + LOGE("Error getting status field"); + return NULL; + } + + typeField = lookup.findField(vnetConfigClass, "type", "Lcom/zerotier/sdk/VirtualNetworkType;"); + if(env->ExceptionCheck() || typeField == NULL) + { + LOGE("Error getting type field"); + return NULL; + } + + mtuField = lookup.findField(vnetConfigClass, "mtu", "I"); + if(env->ExceptionCheck() || mtuField == NULL) + { + LOGE("Error getting mtu field"); + return NULL; + } + + dhcpField = lookup.findField(vnetConfigClass, "dhcp", "Z"); + if(env->ExceptionCheck() || dhcpField == NULL) + { + LOGE("Error getting dhcp field"); + return NULL; + } + + bridgeField = lookup.findField(vnetConfigClass, "bridge", "Z"); + if(env->ExceptionCheck() || bridgeField == NULL) + { + LOGE("Error getting bridge field"); + return NULL; + } + + broadcastEnabledField = lookup.findField(vnetConfigClass, "broadcastEnabled", "Z"); + if(env->ExceptionCheck() || broadcastEnabledField == NULL) + { + LOGE("Error getting broadcastEnabled field"); + return NULL; + } + + portErrorField = lookup.findField(vnetConfigClass, "portError", "I"); + if(env->ExceptionCheck() || portErrorField == NULL) + { + LOGE("Error getting portError field"); + return NULL; + } + + netconfRevisionField = lookup.findField(vnetConfigClass, "netconfRevision", "J"); + if(env->ExceptionCheck() || netconfRevisionField == NULL) + { + LOGE("Error getting netconfRevision field"); + return NULL; + } + + assignedAddressesField = lookup.findField(vnetConfigClass, "assignedAddresses", + "[Ljava/net/InetSocketAddress;"); + if(env->ExceptionCheck() || assignedAddressesField == NULL) + { + LOGE("Error getting assignedAddresses field"); + return NULL; + } + + routesField = lookup.findField(vnetConfigClass, "routes", + "[Lcom/zerotier/sdk/VirtualNetworkRoute;"); + if(env->ExceptionCheck() || routesField == NULL) + { + LOGE("Error getting routes field"); + return NULL; + } + + env->SetLongField(vnetConfigObj, nwidField, vnetConfig.nwid); + env->SetLongField(vnetConfigObj, macField, vnetConfig.mac); + jstring nameStr = env->NewStringUTF(vnetConfig.name); + if(env->ExceptionCheck() || nameStr == NULL) + { + return NULL; // out of memory + } + env->SetObjectField(vnetConfigObj, nameField, nameStr); + + jobject statusObject = createVirtualNetworkStatus(env, vnetConfig.status); + if(env->ExceptionCheck() || statusObject == NULL) + { + return NULL; + } + env->SetObjectField(vnetConfigObj, statusField, statusObject); + + jobject typeObject = createVirtualNetworkType(env, vnetConfig.type); + if(env->ExceptionCheck() || typeObject == NULL) + { + return NULL; + } + env->SetObjectField(vnetConfigObj, typeField, typeObject); + + env->SetIntField(vnetConfigObj, mtuField, (int)vnetConfig.mtu); + env->SetBooleanField(vnetConfigObj, dhcpField, vnetConfig.dhcp); + env->SetBooleanField(vnetConfigObj, bridgeField, vnetConfig.bridge); + env->SetBooleanField(vnetConfigObj, broadcastEnabledField, vnetConfig.broadcastEnabled); + env->SetIntField(vnetConfigObj, portErrorField, vnetConfig.portError); + + jclass inetSocketAddressClass = lookup.findClass("java/net/InetSocketAddress"); + if(env->ExceptionCheck() || inetSocketAddressClass == NULL) + { + LOGE("Error finding InetSocketAddress class"); + return NULL; + } + + jobjectArray assignedAddrArrayObj = env->NewObjectArray( + vnetConfig.assignedAddressCount, inetSocketAddressClass, NULL); + if(env->ExceptionCheck() || assignedAddrArrayObj == NULL) + { + LOGE("Error creating InetSocketAddress[] array"); + return NULL; + } + + for(unsigned int i = 0; i < vnetConfig.assignedAddressCount; ++i) + { + jobject inetAddrObj = newInetSocketAddress(env, vnetConfig.assignedAddresses[i]); + env->SetObjectArrayElement(assignedAddrArrayObj, i, inetAddrObj); + if(env->ExceptionCheck()) + { + LOGE("Error assigning InetSocketAddress to array"); + return NULL; + } + } + + env->SetObjectField(vnetConfigObj, assignedAddressesField, assignedAddrArrayObj); + + jclass virtualNetworkRouteClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkRoute"); + if(env->ExceptionCheck() || virtualNetworkRouteClass == NULL) + { + LOGE("Error finding VirtualNetworkRoute class"); + return NULL; + } + + jobjectArray routesArrayObj = env->NewObjectArray( + vnetConfig.routeCount, virtualNetworkRouteClass, NULL); + if(env->ExceptionCheck() || routesArrayObj == NULL) + { + LOGE("Error creating VirtualNetworkRoute[] array"); + return NULL; + } + + for(unsigned int i = 0; i < vnetConfig.routeCount; ++i) + { + jobject routeObj = newVirtualNetworkRoute(env, vnetConfig.routes[i]); + env->SetObjectArrayElement(routesArrayObj, i, routeObj); + if(env->ExceptionCheck()) + { + LOGE("Error assigning VirtualNetworkRoute to array"); + return NULL; + } + } + + env->SetObjectField(vnetConfigObj, routesField, routesArrayObj); + + return vnetConfigObj; +} + +jobject newVersion(JNIEnv *env, int major, int minor, int rev) +{ + // create a com.zerotier.sdk.Version object + jclass versionClass = NULL; + jmethodID versionConstructor = NULL; + + versionClass = lookup.findClass("com/zerotier/sdk/Version"); + if(env->ExceptionCheck() || versionClass == NULL) + { + return NULL; + } + + versionConstructor = lookup.findMethod( + versionClass, "", "()V"); + if(env->ExceptionCheck() || versionConstructor == NULL) + { + return NULL; + } + + jobject versionObj = env->NewObject(versionClass, versionConstructor); + if(env->ExceptionCheck() || versionObj == NULL) + { + return NULL; + } + + // copy data to Version object + jfieldID majorField = NULL; + jfieldID minorField = NULL; + jfieldID revisionField = NULL; + + majorField = lookup.findField(versionClass, "major", "I"); + if(env->ExceptionCheck() || majorField == NULL) + { + return NULL; + } + + minorField = lookup.findField(versionClass, "minor", "I"); + if(env->ExceptionCheck() || minorField == NULL) + { + return NULL; + } + + revisionField = lookup.findField(versionClass, "revision", "I"); + if(env->ExceptionCheck() || revisionField == NULL) + { + return NULL; + } + + env->SetIntField(versionObj, majorField, (jint)major); + env->SetIntField(versionObj, minorField, (jint)minor); + env->SetIntField(versionObj, revisionField, (jint)rev); + + return versionObj; +} + +jobject newVirtualNetworkRoute(JNIEnv *env, const ZT_VirtualNetworkRoute &route) +{ + jclass virtualNetworkRouteClass = NULL; + jmethodID routeConstructor = NULL; + + virtualNetworkRouteClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkRoute"); + if(env->ExceptionCheck() || virtualNetworkRouteClass == NULL) + { + return NULL; + } + + routeConstructor = lookup.findMethod(virtualNetworkRouteClass, "", "()V"); + if(env->ExceptionCheck() || routeConstructor == NULL) + { + return NULL; + } + + jobject routeObj = env->NewObject(virtualNetworkRouteClass, routeConstructor); + if(env->ExceptionCheck() || routeObj == NULL) + { + return NULL; + } + + jfieldID targetField = NULL; + jfieldID viaField = NULL; + jfieldID flagsField = NULL; + jfieldID metricField = NULL; + + targetField = lookup.findField(virtualNetworkRouteClass, "target", + "Ljava/net/InetSocketAddress;"); + if(env->ExceptionCheck() || targetField == NULL) + { + return NULL; + } + + viaField = lookup.findField(virtualNetworkRouteClass, "via", + "Ljava/net/InetSocketAddress;"); + if(env->ExceptionCheck() || targetField == NULL) + { + return NULL; + } + + flagsField = lookup.findField(virtualNetworkRouteClass, "flags", "I"); + if(env->ExceptionCheck() || flagsField == NULL) + { + return NULL; + } + + metricField = lookup.findField(virtualNetworkRouteClass, "metric", "I"); + if(env->ExceptionCheck() || metricField == NULL) + { + return NULL; + } + + jobject targetObj = newInetSocketAddress(env, route.target); + jobject viaObj = newInetSocketAddress(env, route.via); + + env->SetObjectField(routeObj, targetField, targetObj); + env->SetObjectField(routeObj, viaField, viaObj); + env->SetIntField(routeObj, flagsField, (jint)route.flags); + env->SetIntField(routeObj, metricField, (jint)route.metric); + + return routeObj; +} + +#ifdef __cplusplus +} +#endif + diff --git a/java/jni/ZT_jniutils.h b/java/jni/ZT_jniutils.h new file mode 100644 index 0000000..e35d4f4 --- /dev/null +++ b/java/jni/ZT_jniutils.h @@ -0,0 +1,69 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_jniutils_h_ +#define ZT_jniutils_h_ +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define LOG_TAG "ZeroTierOneJNI" + +#if __ANDROID__ +#include +#define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) +#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) +#else +#define LOGV(...) fprintf(stdout, __VA_ARGS__) +#define LOGI(...) fprintf(stdout, __VA_ARGS__) +#define LOGD(...) fprintf(stdout, __VA_ARGS__) +#define LOGE(...) fprintf(stdout, __VA_ARGS__) +#endif + +jobject createResultObject(JNIEnv *env, ZT_ResultCode code); +jobject createVirtualNetworkStatus(JNIEnv *env, ZT_VirtualNetworkStatus status); +jobject createVirtualNetworkType(JNIEnv *env, ZT_VirtualNetworkType type); +jobject createEvent(JNIEnv *env, ZT_Event event); +jobject createPeerRole(JNIEnv *env, ZT_PeerRole role); +jobject createVirtualNetworkConfigOperation(JNIEnv *env, ZT_VirtualNetworkConfigOperation op); + +jobject newInetSocketAddress(JNIEnv *env, const sockaddr_storage &addr); +jobject newInetAddress(JNIEnv *env, const sockaddr_storage &addr); + +jobject newMulticastGroup(JNIEnv *env, const ZT_MulticastGroup &mc); + +jobject newPeer(JNIEnv *env, const ZT_Peer &peer); +jobject newPeerPhysicalPath(JNIEnv *env, const ZT_PeerPhysicalPath &ppp); + +jobject newNetworkConfig(JNIEnv *env, const ZT_VirtualNetworkConfig &config); + +jobject newVersion(JNIEnv *env, int major, int minor, int rev); + +jobject newVirtualNetworkRoute(JNIEnv *env, const ZT_VirtualNetworkRoute &route); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/java/jni/com_zerotierone_sdk_Node.cpp b/java/jni/com_zerotierone_sdk_Node.cpp new file mode 100644 index 0000000..defbe7f --- /dev/null +++ b/java/jni/com_zerotierone_sdk_Node.cpp @@ -0,0 +1,1616 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include "com_zerotierone_sdk_Node.h" +#include "ZT_jniutils.h" +#include "ZT_jnilookup.h" + +#include +#include "Mutex.hpp" + +#include +#include +#include +#include + +// global static JNI Lookup Object +JniLookup lookup; + +#ifdef __cplusplus +extern "C" { +#endif + +namespace { + struct JniRef + { + JniRef() + : jvm(NULL) + , node(NULL) + , dataStoreGetListener(NULL) + , dataStorePutListener(NULL) + , packetSender(NULL) + , eventListener(NULL) + , frameListener(NULL) + , configListener(NULL) + , pathChecker(NULL) + , callbacks(NULL) + { + callbacks = (ZT_Node_Callbacks*)malloc(sizeof(ZT_Node_Callbacks)); + memset(callbacks, 0, sizeof(ZT_Node_Callbacks)); + } + + ~JniRef() + { + JNIEnv *env = NULL; + jvm->GetEnv((void**)&env, JNI_VERSION_1_6); + + env->DeleteGlobalRef(dataStoreGetListener); + env->DeleteGlobalRef(dataStorePutListener); + env->DeleteGlobalRef(packetSender); + env->DeleteGlobalRef(eventListener); + env->DeleteGlobalRef(frameListener); + env->DeleteGlobalRef(configListener); + env->DeleteGlobalRef(pathChecker); + + free(callbacks); + callbacks = NULL; + } + + uint64_t id; + + JavaVM *jvm; + + ZT_Node *node; + + jobject dataStoreGetListener; + jobject dataStorePutListener; + jobject packetSender; + jobject eventListener; + jobject frameListener; + jobject configListener; + jobject pathChecker; + + ZT_Node_Callbacks *callbacks; + }; + + + int VirtualNetworkConfigFunctionCallback( + ZT_Node *node, + void *userData, + void *threadData, + uint64_t nwid, + void **, + enum ZT_VirtualNetworkConfigOperation operation, + const ZT_VirtualNetworkConfig *config) + { + LOGV("VritualNetworkConfigFunctionCallback"); + JniRef *ref = (JniRef*)userData; + JNIEnv *env = NULL; + ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6); + + jclass configListenerClass = env->GetObjectClass(ref->configListener); + if(configListenerClass == NULL) + { + LOGE("Couldn't find class for VirtualNetworkConfigListener instance"); + return -1; + } + + jmethodID configListenerCallbackMethod = lookup.findMethod(configListenerClass, + "onNetworkConfigurationUpdated", + "(JLcom/zerotier/sdk/VirtualNetworkConfigOperation;Lcom/zerotier/sdk/VirtualNetworkConfig;)I"); + if(configListenerCallbackMethod == NULL) + { + LOGE("Couldn't find onVirtualNetworkFrame() method"); + return -2; + } + + jobject operationObject = createVirtualNetworkConfigOperation(env, operation); + if(operationObject == NULL) + { + LOGE("Error creating VirtualNetworkConfigOperation object"); + return -3; + } + + jobject networkConfigObject = newNetworkConfig(env, *config); + if(networkConfigObject == NULL) + { + LOGE("Error creating VirtualNetworkConfig object"); + return -4; + } + + return env->CallIntMethod( + ref->configListener, + configListenerCallbackMethod, + (jlong)nwid, operationObject, networkConfigObject); + } + + void VirtualNetworkFrameFunctionCallback(ZT_Node *node, + void *userData, + void *threadData, + uint64_t nwid, + void**, + uint64_t sourceMac, + uint64_t destMac, + unsigned int etherType, + unsigned int vlanid, + const void *frameData, + unsigned int frameLength) + { + LOGV("VirtualNetworkFrameFunctionCallback"); + unsigned char* local = (unsigned char*)frameData; + LOGV("Type Bytes: 0x%02x%02x", local[12], local[13]); + JniRef *ref = (JniRef*)userData; + assert(ref->node == node); + JNIEnv *env = NULL; + ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6); + + + jclass frameListenerClass = env->GetObjectClass(ref->frameListener); + if(env->ExceptionCheck() || frameListenerClass == NULL) + { + LOGE("Couldn't find class for VirtualNetworkFrameListener instance"); + return; + } + + jmethodID frameListenerCallbackMethod = lookup.findMethod( + frameListenerClass, + "onVirtualNetworkFrame", "(JJJJJ[B)V"); + if(env->ExceptionCheck() || frameListenerCallbackMethod == NULL) + { + LOGE("Couldn't find onVirtualNetworkFrame() method"); + return; + } + + jbyteArray dataArray = env->NewByteArray(frameLength); + if(env->ExceptionCheck() || dataArray == NULL) + { + LOGE("Couldn't create frame data array"); + return; + } + + void *data = env->GetPrimitiveArrayCritical(dataArray, NULL); + memcpy(data, frameData, frameLength); + env->ReleasePrimitiveArrayCritical(dataArray, data, 0); + + if(env->ExceptionCheck()) + { + LOGE("Error setting frame data to array"); + return; + } + + env->CallVoidMethod(ref->frameListener, frameListenerCallbackMethod, (jlong)nwid, (jlong)sourceMac, (jlong)destMac, (jlong)etherType, (jlong)vlanid, dataArray); + } + + + void EventCallback(ZT_Node *node, + void *userData, + void *threadData, + enum ZT_Event event, + const void *data) + { + LOGV("EventCallback"); + JniRef *ref = (JniRef*)userData; + if(ref->node != node && event != ZT_EVENT_UP) + { + LOGE("Nodes not equal. ref->node %p, node %p. Event: %d", ref->node, node, event); + return; + } + JNIEnv *env = NULL; + ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6); + + + jclass eventListenerClass = env->GetObjectClass(ref->eventListener); + if(eventListenerClass == NULL) + { + LOGE("Couldn't class for EventListener instance"); + return; + } + + jmethodID onEventMethod = lookup.findMethod(eventListenerClass, + "onEvent", "(Lcom/zerotier/sdk/Event;)V"); + if(onEventMethod == NULL) + { + LOGE("Couldn't find onEvent method"); + return; + } + + jmethodID onTraceMethod = lookup.findMethod(eventListenerClass, + "onTrace", "(Ljava/lang/String;)V"); + if(onTraceMethod == NULL) + { + LOGE("Couldn't find onTrace method"); + return; + } + + jobject eventObject = createEvent(env, event); + if(eventObject == NULL) + { + return; + } + + switch(event) + { + case ZT_EVENT_UP: + { + LOGD("Event Up"); + env->CallVoidMethod(ref->eventListener, onEventMethod, eventObject); + break; + } + case ZT_EVENT_OFFLINE: + { + LOGD("Event Offline"); + env->CallVoidMethod(ref->eventListener, onEventMethod, eventObject); + break; + } + case ZT_EVENT_ONLINE: + { + LOGD("Event Online"); + env->CallVoidMethod(ref->eventListener, onEventMethod, eventObject); + break; + } + case ZT_EVENT_DOWN: + { + LOGD("Event Down"); + env->CallVoidMethod(ref->eventListener, onEventMethod, eventObject); + break; + } + case ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION: + { + LOGV("Identity Collision"); + // call onEvent() + env->CallVoidMethod(ref->eventListener, onEventMethod, eventObject); + } + break; + case ZT_EVENT_TRACE: + { + LOGV("Trace Event"); + // call onTrace() + if(data != NULL) + { + const char* message = (const char*)data; + jstring messageStr = env->NewStringUTF(message); + env->CallVoidMethod(ref->eventListener, onTraceMethod, messageStr); + } + } + break; + case ZT_EVENT_USER_MESSAGE: + break; + } + } + + long DataStoreGetFunction(ZT_Node *node, + void *userData, + void *threadData, + const char *objectName, + void *buffer, + unsigned long bufferSize, + unsigned long bufferIndex, + unsigned long *out_objectSize) + { + JniRef *ref = (JniRef*)userData; + JNIEnv *env = NULL; + ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6); + + jclass dataStoreGetClass = env->GetObjectClass(ref->dataStoreGetListener); + if(dataStoreGetClass == NULL) + { + LOGE("Couldn't find class for DataStoreGetListener instance"); + return -2; + } + + jmethodID dataStoreGetCallbackMethod = lookup.findMethod( + dataStoreGetClass, + "onDataStoreGet", + "(Ljava/lang/String;[BJ[J)J"); + if(dataStoreGetCallbackMethod == NULL) + { + LOGE("Couldn't find onDataStoreGet method"); + return -2; + } + + jstring nameStr = env->NewStringUTF(objectName); + if(nameStr == NULL) + { + LOGE("Error creating name string object"); + return -2; // out of memory + } + + jbyteArray bufferObj = env->NewByteArray(bufferSize); + if(bufferObj == NULL) + { + LOGE("Error creating byte[] buffer of size: %lu", bufferSize); + return -2; + } + + jlongArray objectSizeObj = env->NewLongArray(1); + if(objectSizeObj == NULL) + { + LOGE("Error creating long[1] array for actual object size"); + return -2; // couldn't create long[1] array + } + + LOGV("Calling onDataStoreGet(%s, %p, %lu, %p)", + objectName, buffer, bufferIndex, objectSizeObj); + + long retval = (long)env->CallLongMethod( + ref->dataStoreGetListener, dataStoreGetCallbackMethod, + nameStr, bufferObj, (jlong)bufferIndex, objectSizeObj); + + if(retval > 0) + { + void *data = env->GetPrimitiveArrayCritical(bufferObj, NULL); + memcpy(buffer, data, retval); + env->ReleasePrimitiveArrayCritical(bufferObj, data, 0); + + jlong *objSize = (jlong*)env->GetPrimitiveArrayCritical(objectSizeObj, NULL); + *out_objectSize = (unsigned long)objSize[0]; + env->ReleasePrimitiveArrayCritical(objectSizeObj, objSize, 0); + } + + LOGV("Out Object Size: %lu", *out_objectSize); + + return retval; + } + + int DataStorePutFunction(ZT_Node *node, + void *userData, + void *threadData, + const char *objectName, + const void *buffer, + unsigned long bufferSize, + int secure) + { + JniRef *ref = (JniRef*)userData; + JNIEnv *env = NULL; + ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6); + + + jclass dataStorePutClass = env->GetObjectClass(ref->dataStorePutListener); + if(dataStorePutClass == NULL) + { + LOGE("Couldn't find class for DataStorePutListener instance"); + return -1; + } + + jmethodID dataStorePutCallbackMethod = lookup.findMethod( + dataStorePutClass, + "onDataStorePut", + "(Ljava/lang/String;[BZ)I"); + if(dataStorePutCallbackMethod == NULL) + { + LOGE("Couldn't find onDataStorePut method"); + return -2; + } + + jmethodID deleteMethod = lookup.findMethod(dataStorePutClass, + "onDelete", "(Ljava/lang/String;)I"); + if(deleteMethod == NULL) + { + LOGE("Couldn't find onDelete method"); + return -3; + } + + jstring nameStr = env->NewStringUTF(objectName); + + if(buffer == NULL) + { + LOGD("JNI: Delete file: %s", objectName); + // delete operation + return env->CallIntMethod( + ref->dataStorePutListener, deleteMethod, nameStr); + } + else + { + LOGD("JNI: Write file: %s", objectName); + // set operation + jbyteArray bufferObj = env->NewByteArray(bufferSize); + if(env->ExceptionCheck() || bufferObj == NULL) + { + LOGE("Error creating byte array buffer!"); + return -4; + } + + env->SetByteArrayRegion(bufferObj, 0, bufferSize, (jbyte*)buffer); + bool bsecure = secure != 0; + + return env->CallIntMethod(ref->dataStorePutListener, + dataStorePutCallbackMethod, + nameStr, bufferObj, bsecure); + } + } + + int WirePacketSendFunction(ZT_Node *node, + void *userData, + void *threadData, + const struct sockaddr_storage *localAddress, + const struct sockaddr_storage *remoteAddress, + const void *buffer, + unsigned int bufferSize, + unsigned int ttl) + { + LOGV("WirePacketSendFunction(%p, %p, %p, %d)", localAddress, remoteAddress, buffer, bufferSize); + JniRef *ref = (JniRef*)userData; + assert(ref->node == node); + + JNIEnv *env = NULL; + ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6); + + + jclass packetSenderClass = env->GetObjectClass(ref->packetSender); + if(packetSenderClass == NULL) + { + LOGE("Couldn't find class for PacketSender instance"); + return -1; + } + + jmethodID packetSenderCallbackMethod = lookup.findMethod(packetSenderClass, + "onSendPacketRequested", "(Ljava/net/InetSocketAddress;Ljava/net/InetSocketAddress;[BI)I"); + if(packetSenderCallbackMethod == NULL) + { + LOGE("Couldn't find onSendPacketRequested method"); + return -2; + } + + jobject localAddressObj = NULL; + if(memcmp(localAddress, &ZT_SOCKADDR_NULL, sizeof(sockaddr_storage)) != 0) + { + localAddressObj = newInetSocketAddress(env, *localAddress); + } + + jobject remoteAddressObj = newInetSocketAddress(env, *remoteAddress); + jbyteArray bufferObj = env->NewByteArray(bufferSize); + env->SetByteArrayRegion(bufferObj, 0, bufferSize, (jbyte*)buffer); + int retval = env->CallIntMethod(ref->packetSender, packetSenderCallbackMethod, localAddressObj, remoteAddressObj, bufferObj); + + LOGV("JNI Packet Sender returned: %d", retval); + return retval; + } + + int PathCheckFunction(ZT_Node *node, + void *userPtr, + void *threadPtr, + uint64_t address, + const struct sockaddr_storage *localAddress, + const struct sockaddr_storage *remoteAddress) + { + JniRef *ref = (JniRef*)userPtr; + assert(ref->node == node); + + if(ref->pathChecker == NULL) { + return true; + } + + JNIEnv *env = NULL; + ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6); + + jclass pathCheckerClass = env->GetObjectClass(ref->pathChecker); + if(pathCheckerClass == NULL) + { + LOGE("Couldn't find class for PathChecker instance"); + return true; + } + + jmethodID pathCheckCallbackMethod = lookup.findMethod(pathCheckerClass, + "onPathCheck", "(JLjava/net/InetSocketAddress;Ljava/net/InetSocketAddress;)Z"); + if(pathCheckCallbackMethod == NULL) + { + LOGE("Couldn't find onPathCheck method implementation"); + return true; + } + + jobject localAddressObj = NULL; + jobject remoteAddressObj = NULL; + + if(memcmp(localAddress, &ZT_SOCKADDR_NULL, sizeof(sockaddr_storage)) != 0) + { + localAddressObj = newInetSocketAddress(env, *localAddress); + } + if(memcmp(remoteAddress, &ZT_SOCKADDR_NULL, sizeof(sockaddr_storage)) != 0) + { + remoteAddressObj = newInetSocketAddress(env, *remoteAddress); + } + + return env->CallBooleanMethod(ref->pathChecker, pathCheckCallbackMethod, address, localAddressObj, remoteAddressObj); + } + + int PathLookupFunction(ZT_Node *node, + void *userPtr, + void *threadPtr, + uint64_t address, + int ss_family, + struct sockaddr_storage *result) + { + JniRef *ref = (JniRef*)userPtr; + assert(ref->node == node); + + if(ref->pathChecker == NULL) { + return false; + } + + JNIEnv *env = NULL; + ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6); + + jclass pathCheckerClass = env->GetObjectClass(ref->pathChecker); + if(pathCheckerClass == NULL) + { + LOGE("Couldn't find class for PathChecker instance"); + return false; + } + + jmethodID pathLookupMethod = lookup.findMethod(pathCheckerClass, + "onPathLookup", "(JI)Ljava/net/InetSocketAddress;"); + if(pathLookupMethod == NULL) { + return false; + } + + jobject sockAddressObject = env->CallObjectMethod(ref->pathChecker, pathLookupMethod, address, ss_family); + if(sockAddressObject == NULL) + { + LOGE("Unable to call onPathLookup implementation"); + return false; + } + + jclass inetSockAddressClass = env->GetObjectClass(sockAddressObject); + if(inetSockAddressClass == NULL) + { + LOGE("Unable to find InetSocketAddress class"); + return false; + } + + jmethodID getAddressMethod = lookup.findMethod(inetSockAddressClass, "getAddress", "()Ljava/net/InetAddress;"); + if(getAddressMethod == NULL) + { + LOGE("Unable to find InetSocketAddress.getAddress() method"); + return false; + } + + jmethodID getPortMethod = lookup.findMethod(inetSockAddressClass, "getPort", "()I"); + if(getPortMethod == NULL) + { + LOGE("Unable to find InetSocketAddress.getPort() method"); + return false; + } + + jint port = env->CallIntMethod(sockAddressObject, getPortMethod); + jobject addressObject = env->CallObjectMethod(sockAddressObject, getAddressMethod); + + jclass inetAddressClass = lookup.findClass("java/net/InetAddress"); + if(inetAddressClass == NULL) + { + LOGE("Unable to find InetAddress class"); + return false; + } + + getAddressMethod = lookup.findMethod(inetAddressClass, "getAddress", "()[B"); + if(getAddressMethod == NULL) + { + LOGE("Unable to find InetAddress.getAddress() method"); + return false; + } + + jbyteArray addressBytes = (jbyteArray)env->CallObjectMethod(addressObject, getAddressMethod); + if(addressBytes == NULL) + { + LOGE("Unable to call InetAddress.getBytes()"); + return false; + } + + int addressSize = env->GetArrayLength(addressBytes); + if(addressSize == 4) + { + // IPV4 + sockaddr_in *addr = (sockaddr_in*)result; + addr->sin_family = AF_INET; + addr->sin_port = htons(port); + + void *data = env->GetPrimitiveArrayCritical(addressBytes, NULL); + memcpy(&addr->sin_addr, data, 4); + env->ReleasePrimitiveArrayCritical(addressBytes, data, 0); + } + else if (addressSize == 16) + { + // IPV6 + sockaddr_in6 *addr = (sockaddr_in6*)result; + addr->sin6_family = AF_INET6; + addr->sin6_port = htons(port); + void *data = env->GetPrimitiveArrayCritical(addressBytes, NULL); + memcpy(&addr->sin6_addr, data, 16); + env->ReleasePrimitiveArrayCritical(addressBytes, data, 0); + } + else + { + return false; + } + + return true; + } + + typedef std::map NodeMap; + static NodeMap nodeMap; + ZeroTier::Mutex nodeMapMutex; + + ZT_Node* findNode(uint64_t nodeId) + { + ZeroTier::Mutex::Lock lock(nodeMapMutex); + NodeMap::iterator found = nodeMap.find(nodeId); + if(found != nodeMap.end()) + { + JniRef *ref = found->second; + return ref->node; + } + return NULL; + } +} + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) +{ + lookup.setJavaVM(vm); + return JNI_VERSION_1_6; +} + +JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) +{ + +} + + +/* + * Class: com_zerotier_sdk_Node + * Method: node_init + * Signature: (J)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_node_1init( + JNIEnv *env, jobject obj, jlong now) +{ + LOGV("Creating ZT_Node struct"); + jobject resultObject = createResultObject(env, ZT_RESULT_OK); + + ZT_Node *node; + JniRef *ref = new JniRef; + ref->id = (uint64_t)now; + env->GetJavaVM(&ref->jvm); + + jclass cls = env->GetObjectClass(obj); + jfieldID fid = lookup.findField( + cls, "getListener", "Lcom/zerotier/sdk/DataStoreGetListener;"); + + if(fid == NULL) + { + return NULL; // exception already thrown + } + + jobject tmp = env->GetObjectField(obj, fid); + if(tmp == NULL) + { + return NULL; + } + ref->dataStoreGetListener = env->NewGlobalRef(tmp); + + fid = lookup.findField( + cls, "putListener", "Lcom/zerotier/sdk/DataStorePutListener;"); + + if(fid == NULL) + { + return NULL; // exception already thrown + } + + tmp = env->GetObjectField(obj, fid); + if(tmp == NULL) + { + return NULL; + } + ref->dataStorePutListener = env->NewGlobalRef(tmp); + + fid = lookup.findField( + cls, "sender", "Lcom/zerotier/sdk/PacketSender;"); + if(fid == NULL) + { + return NULL; // exception already thrown + } + + tmp = env->GetObjectField(obj, fid); + if(tmp == NULL) + { + return NULL; + } + ref->packetSender = env->NewGlobalRef(tmp); + + fid = lookup.findField( + cls, "frameListener", "Lcom/zerotier/sdk/VirtualNetworkFrameListener;"); + if(fid == NULL) + { + return NULL; // exception already thrown + } + + tmp = env->GetObjectField(obj, fid); + if(tmp == NULL) + { + return NULL; + } + ref->frameListener = env->NewGlobalRef(tmp); + + fid = lookup.findField( + cls, "configListener", "Lcom/zerotier/sdk/VirtualNetworkConfigListener;"); + if(fid == NULL) + { + return NULL; // exception already thrown + } + + tmp = env->GetObjectField(obj, fid); + if(tmp == NULL) + { + return NULL; + } + ref->configListener = env->NewGlobalRef(tmp); + + fid = lookup.findField( + cls, "eventListener", "Lcom/zerotier/sdk/EventListener;"); + if(fid == NULL) + { + return NULL; + } + + tmp = env->GetObjectField(obj, fid); + if(tmp == NULL) + { + return NULL; + } + ref->eventListener = env->NewGlobalRef(tmp); + + fid = lookup.findField( + cls, "pathChecker", "Lcom/zerotier/sdk/PathChecker;"); + if(fid == NULL) + { + LOGE("no path checker?"); + return NULL; + } + + tmp = env->GetObjectField(obj, fid); + if(tmp != NULL) + { + ref->pathChecker = env->NewGlobalRef(tmp); + } + + ref->callbacks->dataStoreGetFunction = &DataStoreGetFunction; + ref->callbacks->dataStorePutFunction = &DataStorePutFunction; + ref->callbacks->wirePacketSendFunction = &WirePacketSendFunction; + ref->callbacks->virtualNetworkFrameFunction = &VirtualNetworkFrameFunctionCallback; + ref->callbacks->virtualNetworkConfigFunction = &VirtualNetworkConfigFunctionCallback; + ref->callbacks->eventCallback = &EventCallback; + ref->callbacks->pathCheckFunction = &PathCheckFunction; + ref->callbacks->pathLookupFunction = &PathLookupFunction; + + ZT_ResultCode rc = ZT_Node_new( + &node, + ref, + NULL, + ref->callbacks, + (uint64_t)now); + + if(rc != ZT_RESULT_OK) + { + LOGE("Error creating Node: %d", rc); + resultObject = createResultObject(env, rc); + if(node) + { + ZT_Node_delete(node); + node = NULL; + } + delete ref; + ref = NULL; + return resultObject; + } + + ZeroTier::Mutex::Lock lock(nodeMapMutex); + ref->node = node; + nodeMap.insert(std::make_pair(ref->id, ref)); + + + return resultObject; +} + +/* + * Class: com_zerotier_sdk_Node + * Method: node_delete + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_zerotier_sdk_Node_node_1delete( + JNIEnv *env, jobject obj, jlong id) +{ + LOGV("Destroying ZT_Node struct"); + uint64_t nodeId = (uint64_t)id; + + NodeMap::iterator found; + { + ZeroTier::Mutex::Lock lock(nodeMapMutex); + found = nodeMap.find(nodeId); + } + + if(found != nodeMap.end()) + { + JniRef *ref = found->second; + nodeMap.erase(found); + + ZT_Node_delete(ref->node); + + delete ref; + ref = NULL; + } + else + { + LOGE("Attempted to delete a node that doesn't exist!"); + } +} + +/* + * Class: com_zerotier_sdk_Node + * Method: processVirtualNetworkFrame + * Signature: (JJJJJII[B[J)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processVirtualNetworkFrame( + JNIEnv *env, jobject obj, + jlong id, + jlong in_now, + jlong in_nwid, + jlong in_sourceMac, + jlong in_destMac, + jint in_etherType, + jint in_vlanId, + jbyteArray in_frameData, + jlongArray out_nextBackgroundTaskDeadline) +{ + uint64_t nodeId = (uint64_t) id; + + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + // cannot find valid node. We should never get here. + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + unsigned int nbtd_len = env->GetArrayLength(out_nextBackgroundTaskDeadline); + if(nbtd_len < 1) + { + // array for next background task length has 0 elements! + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + uint64_t now = (uint64_t)in_now; + uint64_t nwid = (uint64_t)in_nwid; + uint64_t sourceMac = (uint64_t)in_sourceMac; + uint64_t destMac = (uint64_t)in_destMac; + unsigned int etherType = (unsigned int)in_etherType; + unsigned int vlanId = (unsigned int)in_vlanId; + + unsigned int frameLength = env->GetArrayLength(in_frameData); + void *frameData = env->GetPrimitiveArrayCritical(in_frameData, NULL); + void *localData = malloc(frameLength); + memcpy(localData, frameData, frameLength); + env->ReleasePrimitiveArrayCritical(in_frameData, frameData, 0); + + uint64_t nextBackgroundTaskDeadline = 0; + + ZT_ResultCode rc = ZT_Node_processVirtualNetworkFrame( + node, + NULL, + now, + nwid, + sourceMac, + destMac, + etherType, + vlanId, + (const void*)localData, + frameLength, + &nextBackgroundTaskDeadline); + + jlong *outDeadline = (jlong*)env->GetPrimitiveArrayCritical(out_nextBackgroundTaskDeadline, NULL); + outDeadline[0] = (jlong)nextBackgroundTaskDeadline; + env->ReleasePrimitiveArrayCritical(out_nextBackgroundTaskDeadline, outDeadline, 0); + + return createResultObject(env, rc); +} + +/* + * Class: com_zerotier_sdk_Node + * Method: processWirePacket + * Signature: (JJLjava/net/InetSocketAddress;I[B[J)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket( + JNIEnv *env, jobject obj, + jlong id, + jlong in_now, + jobject in_localAddress, + jobject in_remoteAddress, + jbyteArray in_packetData, + jlongArray out_nextBackgroundTaskDeadline) +{ + uint64_t nodeId = (uint64_t) id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + // cannot find valid node. We should never get here. + LOGE("Couldn't find a valid node!"); + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + unsigned int nbtd_len = env->GetArrayLength(out_nextBackgroundTaskDeadline); + if(nbtd_len < 1) + { + LOGE("nbtd_len < 1"); + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + uint64_t now = (uint64_t)in_now; + + // get the java.net.InetSocketAddress class and getAddress() method + jclass inetAddressClass = lookup.findClass("java/net/InetAddress"); + if(inetAddressClass == NULL) + { + LOGE("Can't find InetAddress class"); + // can't find java.net.InetAddress + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + jmethodID getAddressMethod = lookup.findMethod( + inetAddressClass, "getAddress", "()[B"); + if(getAddressMethod == NULL) + { + // cant find InetAddress.getAddres() + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + jclass InetSocketAddressClass = lookup.findClass("java/net/InetSocketAddress"); + if(InetSocketAddressClass == NULL) + { + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + jmethodID inetSockGetAddressMethod = lookup.findMethod( + InetSocketAddressClass, "getAddress", "()Ljava/net/InetAddress;"); + + jobject localAddrObj = NULL; + if(in_localAddress != NULL) + { + localAddrObj = env->CallObjectMethod(in_localAddress, inetSockGetAddressMethod); + } + + jobject remoteAddrObject = env->CallObjectMethod(in_remoteAddress, inetSockGetAddressMethod); + + if(remoteAddrObject == NULL) + { + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + jmethodID inetSock_getPort = lookup.findMethod( + InetSocketAddressClass, "getPort", "()I"); + + if(env->ExceptionCheck() || inetSock_getPort == NULL) + { + LOGE("Couldn't find getPort method on InetSocketAddress"); + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + // call InetSocketAddress.getPort() + int remotePort = env->CallIntMethod(in_remoteAddress, inetSock_getPort); + if(env->ExceptionCheck()) + { + LOGE("Exception calling InetSocketAddress.getPort()"); + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + // Call InetAddress.getAddress() + jbyteArray remoteAddressArray = (jbyteArray)env->CallObjectMethod(remoteAddrObject, getAddressMethod); + if(remoteAddressArray == NULL) + { + LOGE("Unable to call getAddress()"); + // unable to call getAddress() + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + unsigned int addrSize = env->GetArrayLength(remoteAddressArray); + + + sockaddr_storage localAddress = {}; + + if(localAddrObj == NULL) + { + localAddress = ZT_SOCKADDR_NULL; + } + else + { + int localPort = env->CallIntMethod(in_localAddress, inetSock_getPort); + jbyteArray localAddressArray = (jbyteArray)env->CallObjectMethod(localAddrObj, getAddressMethod); + if(localAddressArray != NULL) + { + + unsigned int localAddrSize = env->GetArrayLength(localAddressArray); + jbyte *addr = (jbyte*)env->GetPrimitiveArrayCritical(localAddressArray, NULL); + + if(localAddrSize == 16) + { + sockaddr_in6 ipv6 = {}; + ipv6.sin6_family = AF_INET6; + ipv6.sin6_port = htons(localPort); + memcpy(ipv6.sin6_addr.s6_addr, addr, 16); + memcpy(&localAddress, &ipv6, sizeof(sockaddr_in6)); + } + else if(localAddrSize) + { + // IPV4 address + sockaddr_in ipv4 = {}; + ipv4.sin_family = AF_INET; + ipv4.sin_port = htons(localPort); + memcpy(&ipv4.sin_addr, addr, 4); + memcpy(&localAddress, &ipv4, sizeof(sockaddr_in)); + } + else + { + localAddress = ZT_SOCKADDR_NULL; + } + env->ReleasePrimitiveArrayCritical(localAddressArray, addr, 0); + } + } + + // get the address bytes + jbyte *addr = (jbyte*)env->GetPrimitiveArrayCritical(remoteAddressArray, NULL); + sockaddr_storage remoteAddress = {}; + + if(addrSize == 16) + { + // IPV6 address + sockaddr_in6 ipv6 = {}; + ipv6.sin6_family = AF_INET6; + ipv6.sin6_port = htons(remotePort); + memcpy(ipv6.sin6_addr.s6_addr, addr, 16); + memcpy(&remoteAddress, &ipv6, sizeof(sockaddr_in6)); + } + else if(addrSize == 4) + { + // IPV4 address + sockaddr_in ipv4 = {}; + ipv4.sin_family = AF_INET; + ipv4.sin_port = htons(remotePort); + memcpy(&ipv4.sin_addr, addr, 4); + memcpy(&remoteAddress, &ipv4, sizeof(sockaddr_in)); + } + else + { + LOGE("Unknown IP version"); + // unknown address type + env->ReleasePrimitiveArrayCritical(remoteAddressArray, addr, 0); + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + env->ReleasePrimitiveArrayCritical(remoteAddressArray, addr, 0); + + unsigned int packetLength = env->GetArrayLength(in_packetData); + if(packetLength == 0) + { + LOGE("Empty packet?!?"); + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + void *packetData = env->GetPrimitiveArrayCritical(in_packetData, NULL); + void *localData = malloc(packetLength); + memcpy(localData, packetData, packetLength); + env->ReleasePrimitiveArrayCritical(in_packetData, packetData, 0); + + uint64_t nextBackgroundTaskDeadline = 0; + + ZT_ResultCode rc = ZT_Node_processWirePacket( + node, + NULL, + now, + &localAddress, + &remoteAddress, + localData, + packetLength, + &nextBackgroundTaskDeadline); + if(rc != ZT_RESULT_OK) + { + LOGE("ZT_Node_processWirePacket returned: %d", rc); + } + + free(localData); + + jlong *outDeadline = (jlong*)env->GetPrimitiveArrayCritical(out_nextBackgroundTaskDeadline, NULL); + outDeadline[0] = (jlong)nextBackgroundTaskDeadline; + env->ReleasePrimitiveArrayCritical(out_nextBackgroundTaskDeadline, outDeadline, 0); + + return createResultObject(env, rc); +} + +/* + * Class: com_zerotier_sdk_Node + * Method: processBackgroundTasks + * Signature: (JJ[J)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processBackgroundTasks( + JNIEnv *env, jobject obj, + jlong id, + jlong in_now, + jlongArray out_nextBackgroundTaskDeadline) +{ + uint64_t nodeId = (uint64_t) id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + // cannot find valid node. We should never get here. + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + unsigned int nbtd_len = env->GetArrayLength(out_nextBackgroundTaskDeadline); + if(nbtd_len < 1) + { + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + uint64_t now = (uint64_t)in_now; + uint64_t nextBackgroundTaskDeadline = 0; + + ZT_ResultCode rc = ZT_Node_processBackgroundTasks(node, NULL, now, &nextBackgroundTaskDeadline); + + jlong *outDeadline = (jlong*)env->GetPrimitiveArrayCritical(out_nextBackgroundTaskDeadline, NULL); + outDeadline[0] = (jlong)nextBackgroundTaskDeadline; + env->ReleasePrimitiveArrayCritical(out_nextBackgroundTaskDeadline, outDeadline, 0); + + return createResultObject(env, rc); +} + +/* + * Class: com_zerotier_sdk_Node + * Method: join + * Signature: (JJ)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_join( + JNIEnv *env, jobject obj, jlong id, jlong in_nwid) +{ + uint64_t nodeId = (uint64_t) id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + // cannot find valid node. We should never get here. + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + uint64_t nwid = (uint64_t)in_nwid; + + ZT_ResultCode rc = ZT_Node_join(node, nwid, NULL, NULL); + + return createResultObject(env, rc); +} + +/* + * Class: com_zerotier_sdk_Node + * Method: leave + * Signature: (JJ)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_leave( + JNIEnv *env, jobject obj, jlong id, jlong in_nwid) +{ + uint64_t nodeId = (uint64_t) id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + // cannot find valid node. We should never get here. + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + uint64_t nwid = (uint64_t)in_nwid; + + ZT_ResultCode rc = ZT_Node_leave(node, nwid, NULL, NULL); + + return createResultObject(env, rc); +} + +/* + * Class: com_zerotier_sdk_Node + * Method: multicastSubscribe + * Signature: (JJJJ)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_multicastSubscribe( + JNIEnv *env, jobject obj, + jlong id, + jlong in_nwid, + jlong in_multicastGroup, + jlong in_multicastAdi) +{ + uint64_t nodeId = (uint64_t) id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + // cannot find valid node. We should never get here. + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + uint64_t nwid = (uint64_t)in_nwid; + uint64_t multicastGroup = (uint64_t)in_multicastGroup; + unsigned long multicastAdi = (unsigned long)in_multicastAdi; + + ZT_ResultCode rc = ZT_Node_multicastSubscribe( + node, NULL, nwid, multicastGroup, multicastAdi); + + return createResultObject(env, rc); +} + +/* + * Class: com_zerotier_sdk_Node + * Method: multicastUnsubscribe + * Signature: (JJJJ)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_multicastUnsubscribe( + JNIEnv *env, jobject obj, + jlong id, + jlong in_nwid, + jlong in_multicastGroup, + jlong in_multicastAdi) +{ + uint64_t nodeId = (uint64_t) id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + // cannot find valid node. We should never get here. + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + uint64_t nwid = (uint64_t)in_nwid; + uint64_t multicastGroup = (uint64_t)in_multicastGroup; + unsigned long multicastAdi = (unsigned long)in_multicastAdi; + + ZT_ResultCode rc = ZT_Node_multicastUnsubscribe( + node, nwid, multicastGroup, multicastAdi); + + return createResultObject(env, rc); +} + +/* + * Class: com_zerotier_sdk_Node + * Method: orbit + * Signature: (JJJ)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_orbit( + JNIEnv *env, jobject obj, + jlong id, + jlong in_moonWorldId, + jlong in_moonSeed) +{ + uint64_t nodeId = (uint64_t)id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + uint64_t moonWorldId = (uint64_t)in_moonWorldId; + uint64_t moonSeed = (uint64_t)in_moonSeed; + + ZT_ResultCode rc = ZT_Node_orbit(node, NULL, moonWorldId, moonSeed); + return createResultObject(env, rc); +} + +/* + * Class: com_zerotier_sdk_Node + * Method: deorbit + * Signature: (JJ)L/com/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_deorbit( + JNIEnv *env, jobject obj, + jlong id, + jlong in_moonWorldId) +{ + uint64_t nodeId = (uint64_t)id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL); + } + + uint64_t moonWorldId = (uint64_t)in_moonWorldId; + + ZT_ResultCode rc = ZT_Node_deorbit(node, NULL, moonWorldId); + return createResultObject(env, rc); +} + +/* + * Class: com_zerotier_sdk_Node + * Method: address + * Signature: (J)J + */ +JNIEXPORT jlong JNICALL Java_com_zerotier_sdk_Node_address( + JNIEnv *env , jobject obj, jlong id) +{ + uint64_t nodeId = (uint64_t) id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + // cannot find valid node. We should never get here. + return 0; + } + + uint64_t address = ZT_Node_address(node); + return (jlong)address; +} + +/* + * Class: com_zerotier_sdk_Node + * Method: status + * Signature: (J)Lcom/zerotier/sdk/NodeStatus; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_status + (JNIEnv *env, jobject obj, jlong id) +{ + uint64_t nodeId = (uint64_t) id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + // cannot find valid node. We should never get here. + return 0; + } + + jclass nodeStatusClass = NULL; + jmethodID nodeStatusConstructor = NULL; + + // create a com.zerotier.sdk.NodeStatus object + nodeStatusClass = lookup.findClass("com/zerotier/sdk/NodeStatus"); + if(nodeStatusClass == NULL) + { + return NULL; + } + + nodeStatusConstructor = lookup.findMethod( + nodeStatusClass, "", "()V"); + if(nodeStatusConstructor == NULL) + { + return NULL; + } + + jobject nodeStatusObj = env->NewObject(nodeStatusClass, nodeStatusConstructor); + if(nodeStatusObj == NULL) + { + return NULL; + } + + ZT_NodeStatus nodeStatus; + ZT_Node_status(node, &nodeStatus); + + jfieldID addressField = NULL; + jfieldID publicIdentityField = NULL; + jfieldID secretIdentityField = NULL; + jfieldID onlineField = NULL; + + addressField = lookup.findField(nodeStatusClass, "address", "J"); + if(addressField == NULL) + { + return NULL; + } + + publicIdentityField = lookup.findField(nodeStatusClass, "publicIdentity", "Ljava/lang/String;"); + if(publicIdentityField == NULL) + { + return NULL; + } + + secretIdentityField = lookup.findField(nodeStatusClass, "secretIdentity", "Ljava/lang/String;"); + if(secretIdentityField == NULL) + { + return NULL; + } + + onlineField = lookup.findField(nodeStatusClass, "online", "Z"); + if(onlineField == NULL) + { + return NULL; + } + + env->SetLongField(nodeStatusObj, addressField, nodeStatus.address); + + jstring pubIdentStr = env->NewStringUTF(nodeStatus.publicIdentity); + if(pubIdentStr == NULL) + { + return NULL; // out of memory + } + env->SetObjectField(nodeStatusObj, publicIdentityField, pubIdentStr); + + jstring secIdentStr = env->NewStringUTF(nodeStatus.secretIdentity); + if(secIdentStr == NULL) + { + return NULL; // out of memory + } + env->SetObjectField(nodeStatusObj, secretIdentityField, secIdentStr); + + env->SetBooleanField(nodeStatusObj, onlineField, nodeStatus.online); + + return nodeStatusObj; +} + +/* + * Class: com_zerotier_sdk_Node + * Method: networkConfig + * Signature: (J)Lcom/zerotier/sdk/VirtualNetworkConfig; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_networkConfig( + JNIEnv *env, jobject obj, jlong id, jlong nwid) +{ + uint64_t nodeId = (uint64_t) id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + // cannot find valid node. We should never get here. + return 0; + } + + ZT_VirtualNetworkConfig *vnetConfig = ZT_Node_networkConfig(node, nwid); + + jobject vnetConfigObject = newNetworkConfig(env, *vnetConfig); + + ZT_Node_freeQueryResult(node, vnetConfig); + + return vnetConfigObject; +} + +/* + * Class: com_zerotier_sdk_Node + * Method: version + * Signature: (J)Lcom/zerotier/sdk/Version; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_version( + JNIEnv *env, jobject obj) +{ + int major = 0; + int minor = 0; + int revision = 0; + + ZT_version(&major, &minor, &revision); + + return newVersion(env, major, minor, revision); +} + +/* + * Class: com_zerotier_sdk_Node + * Method: peers + * Signature: (J)[Lcom/zerotier/sdk/Peer; + */ +JNIEXPORT jobjectArray JNICALL Java_com_zerotier_sdk_Node_peers( + JNIEnv *env, jobject obj, jlong id) +{ + uint64_t nodeId = (uint64_t) id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + // cannot find valid node. We should never get here. + return 0; + } + + ZT_PeerList *peerList = ZT_Node_peers(node); + + if(peerList == NULL) + { + LOGE("ZT_Node_peers returned NULL"); + return NULL; + } + + int peerCount = peerList->peerCount * 100; + LOGV("Ensure Local Capacity: %d", peerCount); + if(env->EnsureLocalCapacity(peerCount)) + { + LOGE("EnsureLocalCapacity failed!!"); + ZT_Node_freeQueryResult(node, peerList); + return NULL; + } + + jclass peerClass = lookup.findClass("com/zerotier/sdk/Peer"); + if(env->ExceptionCheck() || peerClass == NULL) + { + LOGE("Error finding Peer class"); + ZT_Node_freeQueryResult(node, peerList); + return NULL; + } + + jobjectArray peerArrayObj = env->NewObjectArray( + peerList->peerCount, peerClass, NULL); + + if(env->ExceptionCheck() || peerArrayObj == NULL) + { + LOGE("Error creating Peer[] array"); + ZT_Node_freeQueryResult(node, peerList); + return NULL; + } + + + for(unsigned int i = 0; i < peerList->peerCount; ++i) + { + jobject peerObj = newPeer(env, peerList->peers[i]); + env->SetObjectArrayElement(peerArrayObj, i, peerObj); + if(env->ExceptionCheck()) + { + LOGE("Error assigning Peer object to array"); + break; + } + } + + ZT_Node_freeQueryResult(node, peerList); + peerList = NULL; + + return peerArrayObj; +} + +/* + * Class: com_zerotier_sdk_Node + * Method: networks + * Signature: (J)[Lcom/zerotier/sdk/VirtualNetworkConfig; + */ +JNIEXPORT jobjectArray JNICALL Java_com_zerotier_sdk_Node_networks( + JNIEnv *env, jobject obj, jlong id) +{ + uint64_t nodeId = (uint64_t) id; + ZT_Node *node = findNode(nodeId); + if(node == NULL) + { + // cannot find valid node. We should never get here. + return 0; + } + + ZT_VirtualNetworkList *networkList = ZT_Node_networks(node); + if(networkList == NULL) + { + return NULL; + } + + jclass vnetConfigClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkConfig"); + if(env->ExceptionCheck() || vnetConfigClass == NULL) + { + LOGE("Error finding VirtualNetworkConfig class"); + ZT_Node_freeQueryResult(node, networkList); + return NULL; + } + + jobjectArray networkListObject = env->NewObjectArray( + networkList->networkCount, vnetConfigClass, NULL); + if(env->ExceptionCheck() || networkListObject == NULL) + { + LOGE("Error creating VirtualNetworkConfig[] array"); + ZT_Node_freeQueryResult(node, networkList); + return NULL; + } + + for(unsigned int i = 0; i < networkList->networkCount; ++i) + { + jobject networkObject = newNetworkConfig(env, networkList->networks[i]); + env->SetObjectArrayElement(networkListObject, i, networkObject); + if(env->ExceptionCheck()) + { + LOGE("Error assigning VirtualNetworkConfig object to array"); + break; + } + } + + ZT_Node_freeQueryResult(node, networkList); + + return networkListObject; +} + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/java/jni/com_zerotierone_sdk_Node.h b/java/jni/com_zerotierone_sdk_Node.h new file mode 100644 index 0000000..7c1011a --- /dev/null +++ b/java/jni/com_zerotierone_sdk_Node.h @@ -0,0 +1,133 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_zerotier_sdk_Node */ + +#ifndef _Included_com_zerotierone_sdk_Node +#define _Included_com_zerotierone_sdk_Node +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_zerotier_sdk_Node + * Method: node_init + * Signature: (J)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_node_1init + (JNIEnv *, jobject, jlong); + +/* + * Class: com_zerotier_sdk_Node + * Method: node_delete + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_zerotier_sdk_Node_node_1delete + (JNIEnv *, jobject, jlong); + +/* + * Class: com_zerotier_sdk_Node + * Method: processVirtualNetworkFrame + * Signature: (JJJJJII[B[J)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processVirtualNetworkFrame + (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong, jint, jint, jbyteArray, jlongArray); + +/* + * Class: com_zerotier_sdk_Node + * Method: processWirePacket + * Signature: (JJLjava/net/InetSockAddress;Ljava/net/InetSockAddress;[B[J)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket + (JNIEnv *, jobject, jlong, jlong, jobject, jobject, jbyteArray, jlongArray); + +/* + * Class: com_zerotier_sdk_Node + * Method: processBackgroundTasks + * Signature: (JJ[J)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processBackgroundTasks + (JNIEnv *, jobject, jlong, jlong, jlongArray); + +/* + * Class: com_zerotier_sdk_Node + * Method: join + * Signature: (JJ)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_join + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_zerotier_sdk_Node + * Method: leave + * Signature: (JJ)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_leave + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_zerotier_sdk_Node + * Method: multicastSubscribe + * Signature: (JJJJ)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_multicastSubscribe + (JNIEnv *, jobject, jlong, jlong, jlong, jlong); + +/* + * Class: com_zerotier_sdk_Node + * Method: multicastUnsubscribe + * Signature: (JJJJ)Lcom/zerotier/sdk/ResultCode; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_multicastUnsubscribe + (JNIEnv *, jobject, jlong, jlong, jlong, jlong); + +/* + * Class: com_zerotier_sdk_Node + * Method: address + * Signature: (J)J + */ +JNIEXPORT jlong JNICALL Java_com_zerotier_sdk_Node_address + (JNIEnv *, jobject, jlong); + +/* + * Class: com_zerotier_sdk_Node + * Method: status + * Signature: (J)Lcom/zerotier/sdk/NodeStatus; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_status + (JNIEnv *, jobject, jlong); + +/* + * Class: com_zerotier_sdk_Node + * Method: networkConfig + * Signature: (JJ)Lcom/zerotier/sdk/VirtualNetworkConfig; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_networkConfig + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_zerotier_sdk_Node + * Method: version + * Signature: ()Lcom/zerotier/sdk/Version; + */ +JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_version + (JNIEnv *, jobject); + +/* + * Class: com_zerotier_sdk_Node + * Method: peers + * Signature: (J)[Lcom/zerotier/sdk/Peer; + */ +JNIEXPORT jobjectArray JNICALL Java_com_zerotier_sdk_Node_peers + (JNIEnv *, jobject, jlong); + +/* + * Class: com_zerotier_sdk_Node + * Method: networks + * Signature: (J)[Lcom/zerotier/sdk/VirtualNetworkConfig; + */ +JNIEXPORT jobjectArray JNICALL Java_com_zerotier_sdk_Node_networks + (JNIEnv *, jobject, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/java/src/com/zerotier/sdk/DataStoreGetListener.java b/java/src/com/zerotier/sdk/DataStoreGetListener.java new file mode 100644 index 0000000..b525be6 --- /dev/null +++ b/java/src/com/zerotier/sdk/DataStoreGetListener.java @@ -0,0 +1,58 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ +package com.zerotier.sdk; + +public interface DataStoreGetListener { + + /** + * Function to get an object from the data store + * + *

Object names can contain forward slash (/) path separators. They will + * never contain .. or backslash (\), so this is safe to map as a Unix-style + * path if the underlying storage permits. For security reasons we recommend + * returning errors if .. or \ are used.

+ * + *

The function must return the actual number of bytes read. If the object + * doesn't exist, it should return -1. -2 should be returned on other errors + * such as errors accessing underlying storage.

+ * + *

If the read doesn't fit in the buffer, the max number of bytes should be + * read. The caller may call the function multiple times to read the whole + * object.

+ * + * @param name Name of the object in the data store + * @param out_buffer buffer to put the object in + * @param bufferIndex index in the object to start reading + * @param out_objectSize long[1] to be set to the actual size of the object if it exists. + * @return the actual number of bytes read. + */ + public long onDataStoreGet( + String name, + byte[] out_buffer, + long bufferIndex, + long[] out_objectSize); +} diff --git a/java/src/com/zerotier/sdk/DataStorePutListener.java b/java/src/com/zerotier/sdk/DataStorePutListener.java new file mode 100644 index 0000000..77e5502 --- /dev/null +++ b/java/src/com/zerotier/sdk/DataStorePutListener.java @@ -0,0 +1,59 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ +package com.zerotier.sdk; + +public interface DataStorePutListener { + + /** + * Function to store an object in the data store + * + *

If secure is true, the file should be set readable and writable only + * to the user running ZeroTier One. What this means is platform-specific.

+ * + *

Name semantics are the same as {@link DataStoreGetListener}. This must return + * zero on success. You can return any OS-specific error code on failure, as these + * may be visible in logs or error messages and might aid in debugging.

+ * + * @param name Object name + * @param buffer data to store + * @param secure set to user read/write only. + * @return 0 on success. + */ + public int onDataStorePut( + String name, + byte[] buffer, + boolean secure); + + /** + * Function to delete an object from the data store + * + * @param name Object name + * @return 0 on success. + */ + public int onDelete( + String name); +} diff --git a/java/src/com/zerotier/sdk/Event.java b/java/src/com/zerotier/sdk/Event.java new file mode 100644 index 0000000..22d350e --- /dev/null +++ b/java/src/com/zerotier/sdk/Event.java @@ -0,0 +1,98 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +public enum Event { + /** + * Node has been initialized + * + * This is the first event generated, and is always sent. It may occur + * before Node's constructor returns. + */ + EVENT_UP, + + /** + * Node is offline -- network does not seem to be reachable by any available strategy + */ + EVENT_OFFLINE, + + /** + * Node is online -- at least one upstream node appears reachable + * + * Meta-data: none + */ + EVENT_ONLINE, + + /** + * Node is shutting down + * + *

This is generated within Node's destructor when it is being shut down. + * It's done for convenience, since cleaning up other state in the event + * handler may appear more idiomatic.

+ */ + EVENT_DOWN, + + /** + * Your identity has collided with another node's ZeroTier address + * + *

This happens if two different public keys both hash (via the algorithm + * in Identity::generate()) to the same 40-bit ZeroTier address.

+ * + *

This is something you should "never" see, where "never" is defined as + * once per 2^39 new node initializations / identity creations. If you do + * see it, you're going to see it very soon after a node is first + * initialized.

+ * + *

This is reported as an event rather than a return code since it's + * detected asynchronously via error messages from authoritative nodes.

+ * + *

If this occurs, you must shut down and delete the node, delete the + * identity.secret record/file from the data store, and restart to generate + * a new identity. If you don't do this, you will not be able to communicate + * with other nodes.

+ * + *

We'd automate this process, but we don't think silently deleting + * private keys or changing our address without telling the calling code + * is good form. It violates the principle of least surprise.

+ * + *

You can technically get away with not handling this, but we recommend + * doing so in a mature reliable application. Besides, handling this + * condition is a good way to make sure it never arises. It's like how + * umbrellas prevent rain and smoke detectors prevent fires. They do, right?

+ */ + EVENT_FATAL_ERROR_IDENTITY_COLLISION, + + /** + * Trace (debugging) message + * + *

These events are only generated if this is a TRACE-enabled build.

+ * + *

Meta-data: {@link String}, TRACE message

+ */ + EVENT_TRACE +} \ No newline at end of file diff --git a/java/src/com/zerotier/sdk/EventListener.java b/java/src/com/zerotier/sdk/EventListener.java new file mode 100644 index 0000000..91050aa --- /dev/null +++ b/java/src/com/zerotier/sdk/EventListener.java @@ -0,0 +1,52 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +import java.net.InetSocketAddress; +import java.lang.String; + +/** + * Interface to handle callbacks for ZeroTier One events. + */ +public interface EventListener { + /** + * Callback for events with no other associated metadata + * + * @param event {@link Event} enum + */ + public void onEvent(Event event); + + /** + * Trace messages + * + *

These events are only generated if the underlying ZeroTierOne SDK is a TRACE-enabled build.

+ * + * @param message the trace message + */ + public void onTrace(String message); +} diff --git a/java/src/com/zerotier/sdk/NativeUtils.java b/java/src/com/zerotier/sdk/NativeUtils.java new file mode 100644 index 0000000..07e1ef5 --- /dev/null +++ b/java/src/com/zerotier/sdk/NativeUtils.java @@ -0,0 +1,93 @@ +package com.zerotier.sdk; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Simple library class for working with JNI (Java Native Interface) + * + * @see http://adamheinrich.com/2012/how-to-load-native-jni-library-from-jar + * + * @author Adam Heirnich , http://www.adamh.cz + */ +public class NativeUtils { + + /** + * Private constructor - this class will never be instanced + */ + private NativeUtils() { + } + + /** + * Loads library from current JAR archive + * + * The file from JAR is copied into system temporary directory and then loaded. The temporary file is deleted after exiting. + * Method uses String as filename because the pathname is "abstract", not system-dependent. + * + * @param filename The filename inside JAR as absolute path (beginning with '/'), e.g. /package/File.ext + * @throws IOException If temporary file creation or read/write operation fails + * @throws IllegalArgumentException If source file (param path) does not exist + * @throws IllegalArgumentException If the path is not absolute or if the filename is shorter than three characters (restriction of {@see File#createTempFile(java.lang.String, java.lang.String)}). + */ + public static void loadLibraryFromJar(String path) throws IOException { + + if (!path.startsWith("/")) { + throw new IllegalArgumentException("The path has to be absolute (start with '/')."); + } + + // Obtain filename from path + String[] parts = path.split("/"); + String filename = (parts.length > 1) ? parts[parts.length - 1] : null; + + // Split filename to prexif and suffix (extension) + String prefix = ""; + String suffix = null; + if (filename != null) { + parts = filename.split("\\.", 2); + prefix = parts[0]; + suffix = (parts.length > 1) ? "."+parts[parts.length - 1] : null; // Thanks, davs! :-) + } + + // Check if the filename is okay + if (filename == null || prefix.length() < 3) { + throw new IllegalArgumentException("The filename has to be at least 3 characters long."); + } + + // Prepare temporary file + File temp = File.createTempFile(prefix, suffix); + temp.deleteOnExit(); + + if (!temp.exists()) { + throw new FileNotFoundException("File " + temp.getAbsolutePath() + " does not exist."); + } + + // Prepare buffer for data copying + byte[] buffer = new byte[1024]; + int readBytes; + + // Open and check input stream + InputStream is = NativeUtils.class.getResourceAsStream(path); + if (is == null) { + throw new FileNotFoundException("File " + path + " was not found inside JAR."); + } + + // Open output stream and copy data between source file in JAR and the temporary file + OutputStream os = new FileOutputStream(temp); + try { + while ((readBytes = is.read(buffer)) != -1) { + os.write(buffer, 0, readBytes); + } + } finally { + // If read/write fails, close streams safely before throwing an exception + os.close(); + is.close(); + } + + // Finally, load the library + System.load(temp.getAbsolutePath()); + } +} \ No newline at end of file diff --git a/java/src/com/zerotier/sdk/Node.java b/java/src/com/zerotier/sdk/Node.java new file mode 100644 index 0000000..8e7d44e --- /dev/null +++ b/java/src/com/zerotier/sdk/Node.java @@ -0,0 +1,475 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.io.IOException; + +/** + * A ZeroTier One node + */ +public class Node { + static { + try { + System.loadLibrary("ZeroTierOneJNI"); + } catch (UnsatisfiedLinkError e) { + try { + if(System.getProperty("os.name").startsWith("Windows")) { + System.out.println("Arch: " + System.getProperty("sun.arch.data.model")); + if(System.getProperty("sun.arch.data.model").equals("64")) { + NativeUtils.loadLibraryFromJar("/lib/ZeroTierOneJNI_win64.dll"); + } else { + NativeUtils.loadLibraryFromJar("/lib/ZeroTierOneJNI_win32.dll"); + } + } else if(System.getProperty("os.name").startsWith("Mac")) { + NativeUtils.loadLibraryFromJar("/lib/libZeroTierOneJNI.jnilib"); + } else { + // TODO: Linux + } + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + } + + private static final String TAG = "NODE"; + + /** + * Node ID for JNI purposes. + * Currently set to the now value passed in at the constructor + * + * -1 if the node has already been closed + */ + private long nodeId; + + private final DataStoreGetListener getListener; + private final DataStorePutListener putListener; + private final PacketSender sender; + private final EventListener eventListener; + private final VirtualNetworkFrameListener frameListener; + private final VirtualNetworkConfigListener configListener; + private final PathChecker pathChecker; + + /** + * Create a new ZeroTier One node + * + *

Note that this can take a few seconds the first time it's called, as it + * will generate an identity.

+ * + * @param now Current clock in milliseconds + * @param getListener User written instance of the {@link DataStoreGetListener} interface called to get objects from persistent storage. This instance must be unique per Node object. + * @param putListener User written intstance of the {@link DataStorePutListener} interface called to put objects in persistent storage. This instance must be unique per Node object. + * @param sender + * @param eventListener User written instance of the {@link EventListener} interface to receive status updates and non-fatal error notices. This instance must be unique per Node object. + * @param frameListener + * @param configListener User written instance of the {@link VirtualNetworkConfigListener} interface to be called when virtual LANs are created, deleted, or their config parameters change. This instance must be unique per Node object. + * @param pathChecker User written instance of the {@link PathChecker} interface. Not required and can be null. + */ + public Node(long now, + DataStoreGetListener getListener, + DataStorePutListener putListener, + PacketSender sender, + EventListener eventListener, + VirtualNetworkFrameListener frameListener, + VirtualNetworkConfigListener configListener, + PathChecker pathChecker) throws NodeException + { + this.nodeId = now; + + this.getListener = getListener; + this.putListener = putListener; + this.sender = sender; + this.eventListener = eventListener; + this.frameListener = frameListener; + this.configListener = configListener; + this.pathChecker = pathChecker; + + ResultCode rc = node_init(now); + if(rc != ResultCode.RESULT_OK) + { + // TODO: Throw Exception + throw new NodeException(rc.toString()); + } + } + + /** + * Close this Node. + * + *

The Node object can no longer be used once this method is called.

+ */ + public void close() { + if(nodeId != -1) { + node_delete(nodeId); + nodeId = -1; + } + } + + @Override + protected void finalize() { + close(); + } + + /** + * Process a frame from a virtual network port + * + * @param now Current clock in milliseconds + * @param nwid ZeroTier 64-bit virtual network ID + * @param sourceMac Source MAC address (least significant 48 bits) + * @param destMac Destination MAC address (least significant 48 bits) + * @param etherType 16-bit Ethernet frame type + * @param vlanId 10-bit VLAN ID or 0 if none + * @param frameData Frame payload data + * @param nextBackgroundTaskDeadline Value/result: set to deadline for next call to processBackgroundTasks() + * @return OK (0) or error code if a fatal error condition has occurred + */ + public ResultCode processVirtualNetworkFrame( + long now, + long nwid, + long sourceMac, + long destMac, + int etherType, + int vlanId, + byte[] frameData, + long[] nextBackgroundTaskDeadline) { + return processVirtualNetworkFrame( + nodeId, now, nwid, sourceMac, destMac, etherType, vlanId, + frameData, nextBackgroundTaskDeadline); + } + + /** + * Process a packet received from the physical wire + * + * @param now Current clock in milliseconds + * @param remoteAddress Origin of packet + * @param packetData Packet data + * @param nextBackgroundTaskDeadline Value/result: set to deadline for next call to processBackgroundTasks() + * @return OK (0) or error code if a fatal error condition has occurred + */ + public ResultCode processWirePacket( + long now, + InetSocketAddress localAddress, + InetSocketAddress remoteAddress, + byte[] packetData, + long[] nextBackgroundTaskDeadline) { + return processWirePacket( + nodeId, now, localAddress, remoteAddress, packetData, + nextBackgroundTaskDeadline); + } + + /** + * Perform periodic background operations + * + * @param now Current clock in milliseconds + * @param nextBackgroundTaskDeadline Value/result: set to deadline for next call to processBackgroundTasks() + * @return OK (0) or error code if a fatal error condition has occurred + */ + public ResultCode processBackgroundTasks(long now, long[] nextBackgroundTaskDeadline) { + return processBackgroundTasks(nodeId, now, nextBackgroundTaskDeadline); + } + + /** + * Join a network + * + *

This may generate calls to the port config callback before it returns, + * or these may be deffered if a netconf is not available yet.

+ * + *

If we are already a member of the network, nothing is done and OK is + * returned.

+ * + * @param nwid 64-bit ZeroTier network ID + * @return OK (0) or error code if a fatal error condition has occurred + */ + public ResultCode join(long nwid) { + return join(nodeId, nwid); + } + + /** + * Leave a network + * + *

If a port has been configured for this network this will generate a call + * to the port config callback with a NULL second parameter to indicate that + * the port is now deleted.

+ * + * @param nwid 64-bit network ID + * @return OK (0) or error code if a fatal error condition has occurred + */ + public ResultCode leave(long nwid) { + return leave(nodeId, nwid); + } + + /** + * Subscribe to an Ethernet multicast group + * + *

For IPv4 ARP, the implementation must subscribe to 0xffffffffffff (the + * broadcast address) but with an ADI equal to each IPv4 address in host + * byte order. This converts ARP from a non-scalable broadcast protocol to + * a scalable multicast protocol with perfect address specificity.

+ * + *

If this is not done, ARP will not work reliably.

+ * + *

Multiple calls to subscribe to the same multicast address will have no + * effect. It is perfectly safe to do this.

+ * + *

This does not generate an update call to the {@link VirtualNetworkConfigListener#onNetworkConfigurationUpdated} method.

+ * + * @param nwid 64-bit network ID + * @param multicastGroup Ethernet multicast or broadcast MAC (least significant 48 bits) + * @return OK (0) or error code if a fatal error condition has occurred + */ + public ResultCode multicastSubscribe( + long nwid, + long multicastGroup) { + return multicastSubscribe(nodeId, nwid, multicastGroup, 0); + } + + /** + * Subscribe to an Ethernet multicast group + * + *

ADI stands for additional distinguishing information. This defaults to zero + * and is rarely used. Right now its only use is to enable IPv4 ARP to scale, + * and this must be done.

+ * + *

For IPv4 ARP, the implementation must subscribe to 0xffffffffffff (the + * broadcast address) but with an ADI equal to each IPv4 address in host + * byte order. This converts ARP from a non-scalable broadcast protocol to + * a scalable multicast protocol with perfect address specificity.

+ * + *

If this is not done, ARP will not work reliably.

+ * + *

Multiple calls to subscribe to the same multicast address will have no + * effect. It is perfectly safe to do this.

+ * + *

This does not generate an update call to the {@link VirtualNetworkConfigListener#onNetworkConfigurationUpdated} method.

+ * + * @param nwid 64-bit network ID + * @param multicastGroup Ethernet multicast or broadcast MAC (least significant 48 bits) + * @param multicastAdi Multicast ADI (least significant 32 bits only, default: 0) + * @return OK (0) or error code if a fatal error condition has occurred + */ + public ResultCode multicastSubscribe( + long nwid, + long multicastGroup, + long multicastAdi) { + return multicastSubscribe(nodeId, nwid, multicastGroup, multicastAdi); + } + + + /** + * Unsubscribe from an Ethernet multicast group (or all groups) + * + *

If multicastGroup is zero (0), this will unsubscribe from all groups. If + * you are not subscribed to a group this has no effect.

+ * + *

This does not generate an update call to the {@link VirtualNetworkConfigListener#onNetworkConfigurationUpdated} method.

+ * + * @param nwid 64-bit network ID + * @param multicastGroup Ethernet multicast or broadcast MAC (least significant 48 bits) + * @return OK (0) or error code if a fatal error condition has occurred + */ + public ResultCode multicastUnsubscribe( + long nwid, + long multicastGroup) { + return multicastUnsubscribe(nodeId, nwid, multicastGroup, 0); + } + + /** + * Unsubscribe from an Ethernet multicast group (or all groups) + * + *

If multicastGroup is zero (0), this will unsubscribe from all groups. If + * you are not subscribed to a group this has no effect.

+ * + *

This does not generate an update call to the {@link VirtualNetworkConfigListener#onNetworkConfigurationUpdated} method.

+ * + *

ADI stands for additional distinguishing information. This defaults to zero + * and is rarely used. Right now its only use is to enable IPv4 ARP to scale, + * and this must be done.

+ * + * @param nwid 64-bit network ID + * @param multicastGroup Ethernet multicast or broadcast MAC (least significant 48 bits) + * @param multicastAdi Multicast ADI (least significant 32 bits only, default: 0) + * @return OK (0) or error code if a fatal error condition has occurred + */ + public ResultCode multicastUnsubscribe( + long nwid, + long multicastGroup, + long multicastAdi) { + return multicastUnsubscribe(nodeId, nwid, multicastGroup, multicastAdi); + } + + /** + * Add or update a moon + * + * Moons are persisted in the data store in moons.d/, so this can persist + * across invocations if the contents of moon.d are scanned and orbit is + * called for each on startup. + * + * @param moonWorldId Moon's world ID + * @param moonSeed If non-zero, the ZeroTier address of any member of the moon to query for moon definition + * @return Error if moon was invalid or failed to be added + */ + public ResultCode orbit( + long moonWorldId, + long moonSeed) { + return orbit(nodeId, moonWorldId, moonSeed); + } + + /** + * Remove a moon (does nothing if not present) + * + * @param moonWorldId World ID of moon to remove + * @return Error if anything bad happened + */ + public ResultCode deorbit( + long moonWorldId) { + return deorbit(nodeId, moonWorldId); + } + + /** + * Get this node's 40-bit ZeroTier address + * + * @return ZeroTier address (least significant 40 bits of 64-bit int) + */ + public long address() { + return address(nodeId); + } + + /** + * Get the status of this node + * + * @return @{link NodeStatus} struct with the current node status. + */ + public NodeStatus status() { + return status(nodeId); + } + + /** + * Get a list of known peer nodes + * + * @return List of known peers or NULL on failure + */ + public Peer[] peers() { + return peers(nodeId); + } + + /** + * Get the status of a virtual network + * + * @param nwid 64-bit network ID + * @return {@link VirtualNetworkConfig} or NULL if we are not a member of this network + */ + public VirtualNetworkConfig networkConfig(long nwid) { + return networkConfig(nodeId, nwid); + } + + /** + * Enumerate and get status of all networks + * + * @return List of networks or NULL on failure + */ + public VirtualNetworkConfig[] networks() { + return networks(nodeId); + } + + /** + * Get ZeroTier One version + * + * @return {@link Version} object with ZeroTierOne version information. + */ + public Version getVersion() { + return version(); + } + + // + // function declarations for JNI + // + private native ResultCode node_init(long now); + + private native void node_delete(long nodeId); + + private native ResultCode processVirtualNetworkFrame( + long nodeId, + long now, + long nwid, + long sourceMac, + long destMac, + int etherType, + int vlanId, + byte[] frameData, + long[] nextBackgroundTaskDeadline); + + private native ResultCode processWirePacket( + long nodeId, + long now, + InetSocketAddress localAddress, + InetSocketAddress remoteAddress, + byte[] packetData, + long[] nextBackgroundTaskDeadline); + + private native ResultCode processBackgroundTasks( + long nodeId, + long now, + long[] nextBackgroundTaskDeadline); + + private native ResultCode join(long nodeId, long nwid); + + private native ResultCode leave(long nodeId, long nwid); + + private native ResultCode multicastSubscribe( + long nodeId, + long nwid, + long multicastGroup, + long multicastAdi); + + private native ResultCode multicastUnsubscribe( + long nodeId, + long nwid, + long multicastGroup, + long multicastAdi); + + private native ResultCode orbit( + long nodeId, + long moonWorldId, + long moonSeed); + + private native ResultCode deorbit( + long nodeId, + long moonWorldId); + + private native long address(long nodeId); + + private native NodeStatus status(long nodeId); + + private native VirtualNetworkConfig networkConfig(long nodeId, long nwid); + + private native Version version(); + + private native Peer[] peers(long nodeId); + + private native VirtualNetworkConfig[] networks(long nodeId); +} \ No newline at end of file diff --git a/java/src/com/zerotier/sdk/NodeException.java b/java/src/com/zerotier/sdk/NodeException.java new file mode 100644 index 0000000..1fdef72 --- /dev/null +++ b/java/src/com/zerotier/sdk/NodeException.java @@ -0,0 +1,36 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +import java.lang.RuntimeException; + +public class NodeException extends RuntimeException { + public NodeException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/java/src/com/zerotier/sdk/NodeStatus.java b/java/src/com/zerotier/sdk/NodeStatus.java new file mode 100644 index 0000000..94376d8 --- /dev/null +++ b/java/src/com/zerotier/sdk/NodeStatus.java @@ -0,0 +1,69 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +public final class NodeStatus { + private long address; + private String publicIdentity; + private String secretIdentity; + private boolean online; + + private NodeStatus() {} + + /** + * 40-bit ZeroTier address of this node + */ + public final long getAddres() { + return address; + } + + /** + * Public identity in string-serialized form (safe to send to others) + * + *

This identity will remain valid as long as the node exists.

+ */ + public final String getPublicIdentity() { + return publicIdentity; + } + + /** + * Full identity including secret key in string-serialized form + * + *

This identity will remain valid as long as the node exists.

+ */ + public final String getSecretIdentity() { + return secretIdentity; + } + + /** + * True if some kind of connectivity appears available + */ + public final boolean isOnline() { + return online; + } +} \ No newline at end of file diff --git a/java/src/com/zerotier/sdk/PacketSender.java b/java/src/com/zerotier/sdk/PacketSender.java new file mode 100644 index 0000000..22893ec --- /dev/null +++ b/java/src/com/zerotier/sdk/PacketSender.java @@ -0,0 +1,50 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ +package com.zerotier.sdk; + +import java.net.InetSocketAddress; + + +public interface PacketSender { + /** + * Function to send a ZeroTier packet out over the wire + * + *

The function must return zero on success and may return any error code + * on failure. Note that success does not (of course) guarantee packet + * delivery. It only means that the packet appears to have been sent.

+ * + * @param localAddr {@link InetSocketAddress} to send from. Set to null if not specified. + * @param remoteAddr {@link InetSocketAddress} to send to + * @param packetData data to send + * @return 0 on success, any error code on failure. + */ + public int onSendPacketRequested( + InetSocketAddress localAddr, + InetSocketAddress remoteAddr, + byte[] packetData, + int ttl); +} diff --git a/java/src/com/zerotier/sdk/PathChecker.java b/java/src/com/zerotier/sdk/PathChecker.java new file mode 100644 index 0000000..3e02f11 --- /dev/null +++ b/java/src/com/zerotier/sdk/PathChecker.java @@ -0,0 +1,45 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2017 ZeroTier, Inc. https://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +import java.net.InetSocketAddress; + +public interface PathChecker { + /** + * Callback to check whether a path should be used for ZeroTier traffic + * + * This function must return true if the path should be used. + * + * If no path check function is specified, ZeroTier will still exclude paths + * that overlap with ZeroTier-assigned and managed IP address blocks. But the + * use of a path check function is recommended to ensure that recursion does + * not occur in cases where addresses are assigned by the OS or managed by + * an out of band mechanism like DHCP. The path check function should examine + * all configured ZeroTier interfaces and check to ensure that the supplied + * addresses will not result in ZeroTier traffic being sent over a ZeroTier + * interface (recursion). + * + * Obviously this is not required in configurations where this can't happen, + * such as network containers or embedded. + * + * @param ztAddress ZeroTier address or 0 for none/any + * @param localAddress Local interface address + * @param remoteAddress remote address + */ + boolean onPathCheck(long ztAddress, InetSocketAddress localAddress, InetSocketAddress remoteAddress); + + /** + * Function to get physical addresses for ZeroTier peers + * + * If provided this function will be occasionally called to get physical + * addresses that might be tried to reach a ZeroTier address. + * + * @param ztAddress ZeroTier address (least significant 40 bits) + * @param ss_family desired address family or -1 for any + * @return address and port of ztAddress or null + */ + InetSocketAddress onPathLookup(long ztAddress, int ss_family); +} diff --git a/java/src/com/zerotier/sdk/Peer.java b/java/src/com/zerotier/sdk/Peer.java new file mode 100644 index 0000000..eb3d713 --- /dev/null +++ b/java/src/com/zerotier/sdk/Peer.java @@ -0,0 +1,94 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +import java.util.ArrayList; + +/** + * Peer status result + */ +public final class Peer { + private long address; + private int versionMajor; + private int versionMinor; + private int versionRev; + private int latency; + private PeerRole role; + private PeerPhysicalPath[] paths; + + private Peer() {} + + /** + * ZeroTier address (40 bits) + */ + public final long address() { + return address; + } + + /** + * Remote major version or -1 if not known + */ + public final int versionMajor() { + return versionMajor; + } + + /** + * Remote minor version or -1 if not known + */ + public final int versionMinor() { + return versionMinor; + } + + /** + * Remote revision or -1 if not known + */ + public final int versionRev() { + return versionRev; + } + + /** + * Last measured latency in milliseconds or zero if unknown + */ + public final int latency() { + return latency; + } + + /** + * What trust hierarchy role does this device have? + */ + public final PeerRole role() { + return role; + } + + /** + * Known network paths to peer + */ + public final PeerPhysicalPath[] paths() { + return paths; + } +} \ No newline at end of file diff --git a/java/src/com/zerotier/sdk/PeerPhysicalPath.java b/java/src/com/zerotier/sdk/PeerPhysicalPath.java new file mode 100644 index 0000000..3f9a861 --- /dev/null +++ b/java/src/com/zerotier/sdk/PeerPhysicalPath.java @@ -0,0 +1,78 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +import java.net.InetSocketAddress; + +/** + * Physical network path to a peer + */ +public final class PeerPhysicalPath { + private InetSocketAddress address; + private long lastSend; + private long lastReceive; + private boolean fixed; + private boolean preferred; + + private PeerPhysicalPath() {} + + /** + * Address of endpoint + */ + public final InetSocketAddress address() { + return address; + } + + /** + * Time of last send in milliseconds or 0 for never + */ + public final long lastSend() { + return lastSend; + } + + /** + * Time of last receive in milliseconds or 0 for never + */ + public final long lastReceive() { + return lastReceive; + } + + /** + * Is path fixed? (i.e. not learned, static) + */ + public final boolean isFixed() { + return fixed; + } + + /** + * Is path preferred? + */ + public final boolean isPreferred() { + return preferred; + } +} \ No newline at end of file diff --git a/java/src/com/zerotier/sdk/PeerRole.java b/java/src/com/zerotier/sdk/PeerRole.java new file mode 100644 index 0000000..fce183d --- /dev/null +++ b/java/src/com/zerotier/sdk/PeerRole.java @@ -0,0 +1,45 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +public enum PeerRole { + /** + * An ordinary node + */ + PEER_ROLE_LEAF, + + /** + * moon root + */ + PEER_ROLE_MOON, + + /** + * planetary root + */ + PEER_ROLE_PLANET +} \ No newline at end of file diff --git a/java/src/com/zerotier/sdk/ResultCode.java b/java/src/com/zerotier/sdk/ResultCode.java new file mode 100644 index 0000000..5da82b3 --- /dev/null +++ b/java/src/com/zerotier/sdk/ResultCode.java @@ -0,0 +1,74 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +/** + * Function return code: OK (0) or error results + * + *

Use {@link ResultCode#isFatal) to check for a fatal error. If a fatal error + * occurs, the node should be considered to not be working correctly. These + * indicate serious problems like an inaccessible data store or a compile + * problem.

+ */ +public enum ResultCode { + /** + * Operation completed normally + */ + RESULT_OK(0), + + // Fatal errors (> 0, < 1000) + /** + * Ran out of memory + */ + RESULT_FATAL_ERROR_OUT_OF_MEMORY(1), + + /** + * Data store is not writable or has failed + */ + RESULT_FATAL_ERROR_DATA_STORE_FAILED(2), + + /** + * Internal error (e.g. unexpected exception indicating bug or build problem) + */ + RESULT_FATAL_ERROR_INTERNAL(3), + + // non-fatal errors + + /** + * Network ID not valid + */ + RESULT_ERROR_NETWORK_NOT_FOUND(1000); + + private final int id; + ResultCode(int id) { this.id = id; } + public int getValue() { return id; } + + public boolean isFatal(int id) { + return (id > 0 && id < 1000); + } +} \ No newline at end of file diff --git a/java/src/com/zerotier/sdk/Version.java b/java/src/com/zerotier/sdk/Version.java new file mode 100644 index 0000000..c93c259 --- /dev/null +++ b/java/src/com/zerotier/sdk/Version.java @@ -0,0 +1,36 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +public final class Version { + private Version() {} + + public int major = 0; + public int minor = 0; + public int revision = 0; +} \ No newline at end of file diff --git a/java/src/com/zerotier/sdk/VirtualNetworkConfig.java b/java/src/com/zerotier/sdk/VirtualNetworkConfig.java new file mode 100644 index 0000000..64512da --- /dev/null +++ b/java/src/com/zerotier/sdk/VirtualNetworkConfig.java @@ -0,0 +1,210 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +import java.lang.Comparable; +import java.lang.Override; +import java.lang.String; +import java.util.ArrayList; +import java.net.InetSocketAddress; + +public final class VirtualNetworkConfig implements Comparable { + public static final int MAX_MULTICAST_SUBSCRIPTIONS = 4096; + public static final int ZT_MAX_ZT_ASSIGNED_ADDRESSES = 16; + + private long nwid; + private long mac; + private String name; + private VirtualNetworkStatus status; + private VirtualNetworkType type; + private int mtu; + private boolean dhcp; + private boolean bridge; + private boolean broadcastEnabled; + private int portError; + private boolean enabled; + private long netconfRevision; + private InetSocketAddress[] assignedAddresses; + private VirtualNetworkRoute[] routes; + + private VirtualNetworkConfig() { + + } + + public boolean equals(VirtualNetworkConfig cfg) { + boolean aaEqual = true; + if(assignedAddresses.length == cfg.assignedAddresses.length) { + for(int i = 0; i < assignedAddresses.length; ++i) { + if(!assignedAddresses[i].equals(cfg.assignedAddresses[i])) { + aaEqual = false; + } + } + } else { + aaEqual = false; + } + + boolean routesEqual = true; + if(routes.length == cfg.routes.length) { + for (int i = 0; i < routes.length; ++i) { + if (!routes[i].equals(cfg.routes[i])) { + routesEqual = false; + } + } + } else { + routesEqual = false; + } + + return nwid == cfg.nwid && + mac == cfg.mac && + name.equals(cfg.name) && + status.equals(cfg.status) && + type.equals(cfg.type) && + mtu == cfg.mtu && + dhcp == cfg.dhcp && + bridge == cfg.bridge && + broadcastEnabled == cfg.broadcastEnabled && + portError == cfg.portError && + enabled == cfg.enabled && + aaEqual && routesEqual; + } + + public int compareTo(VirtualNetworkConfig cfg) { + if(cfg.nwid == this.nwid) { + return 0; + } else { + return this.nwid > cfg.nwid ? 1 : -1; + } + } + + /** + * 64-bit ZeroTier network ID + */ + public final long networkId() { + return nwid; + } + + /** + * Ethernet MAC (40 bits) that should be assigned to port + */ + public final long macAddress() { + return mac; + } + + /** + * Network name (from network configuration master) + */ + public final String name() { + return name; + } + + /** + * Network configuration request status + */ + public final VirtualNetworkStatus networkStatus() { + return status; + } + + /** + * Network type + */ + public final VirtualNetworkType networkType() { + return type; + } + + /** + * Maximum interface MTU + */ + public final int mtu() { + return mtu; + } + + /** + * If the network this port belongs to indicates DHCP availability + * + *

This is a suggestion. The underlying implementation is free to ignore it + * for security or other reasons. This is simply a netconf parameter that + * means 'DHCP is available on this network.'

+ */ + public final boolean isDhcpAvailable() { + return dhcp; + } + + /** + * If this port is allowed to bridge to other networks + * + *

This is informational. If this is false, bridged packets will simply + * be dropped and bridging won't work.

+ */ + public final boolean isBridgeEnabled() { + return bridge; + } + + /** + * If true, this network supports and allows broadcast (ff:ff:ff:ff:ff:ff) traffic + */ + public final boolean broadcastEnabled() { + return broadcastEnabled; + } + + /** + * If the network is in PORT_ERROR state, this is the error most recently returned by the port config callback + */ + public final int portError() { + return portError; + } + + /** + * Network config revision as reported by netconf master + * + *

If this is zero, it means we're still waiting for our netconf.

+ */ + public final long netconfRevision() { + return netconfRevision; + } + + /** + * ZeroTier-assigned addresses (in {@link java.net.InetSocketAddress} objects) + * + * For IP, the port number of the sockaddr_XX structure contains the number + * of bits in the address netmask. Only the IP address and port are used. + * Other fields like interface number can be ignored. + * + * This is only used for ZeroTier-managed address assignments sent by the + * virtual network's configuration master. + */ + public final InetSocketAddress[] assignedAddresses() { + return assignedAddresses; + } + + /** + * ZeroTier-assigned routes (in {@link com.zerotier.sdk.VirtualNetworkRoute} objects) + * + * @return + */ + public final VirtualNetworkRoute[] routes() { return routes; } +} diff --git a/java/src/com/zerotier/sdk/VirtualNetworkConfigListener.java b/java/src/com/zerotier/sdk/VirtualNetworkConfigListener.java new file mode 100644 index 0000000..15ae301 --- /dev/null +++ b/java/src/com/zerotier/sdk/VirtualNetworkConfigListener.java @@ -0,0 +1,60 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + + +package com.zerotier.sdk; + + +public interface VirtualNetworkConfigListener { + /** + * Callback called to update virtual network port configuration + * + *

This can be called at any time to update the configuration of a virtual + * network port. The parameter after the network ID specifies whether this + * port is being brought up, updated, brought down, or permanently deleted. + * + * This in turn should be used by the underlying implementation to create + * and configure tap devices at the OS (or virtual network stack) layer.

+ * + * This should not call {@link Node#multicastSubscribe} or other network-modifying + * methods, as this could cause a deadlock in multithreaded or interrupt + * driven environments. + * + * This must return 0 on success. It can return any OS-dependent error code + * on failure, and this results in the network being placed into the + * PORT_ERROR state. + * + * @param nwid network id + * @param op {@link VirtualNetworkConfigOperation} enum describing the configuration operation + * @param config {@link VirtualNetworkConfig} object with the new configuration + * @return 0 on success + */ + public int onNetworkConfigurationUpdated( + long nwid, + VirtualNetworkConfigOperation op, + VirtualNetworkConfig config); +} \ No newline at end of file diff --git a/java/src/com/zerotier/sdk/VirtualNetworkConfigOperation.java b/java/src/com/zerotier/sdk/VirtualNetworkConfigOperation.java new file mode 100644 index 0000000..b70eb47 --- /dev/null +++ b/java/src/com/zerotier/sdk/VirtualNetworkConfigOperation.java @@ -0,0 +1,49 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ +package com.zerotier.sdk; + +public enum VirtualNetworkConfigOperation { + /** + * Network is coming up (either for the first time or after service restart) + */ + VIRTUAL_NETWORK_CONFIG_OPERATION_UP, + + /** + * Network configuration has been updated + */ + VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE, + + /** + * Network is going down (not permanently) + */ + VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN, + + /** + * Network is going down permanently (leave/delete) + */ + VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY +} diff --git a/java/src/com/zerotier/sdk/VirtualNetworkFrameListener.java b/java/src/com/zerotier/sdk/VirtualNetworkFrameListener.java new file mode 100644 index 0000000..9ad3228 --- /dev/null +++ b/java/src/com/zerotier/sdk/VirtualNetworkFrameListener.java @@ -0,0 +1,48 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +public interface VirtualNetworkFrameListener { + /** + * Function to send a frame out to a virtual network port + * + * @param nwid ZeroTier One network ID + * @param srcMac source MAC address + * @param destMac destination MAC address + * @param ethertype + * @param vlanId + * @param frameData data to send + */ + public void onVirtualNetworkFrame( + long nwid, + long srcMac, + long destMac, + long etherType, + long vlanId, + byte[] frameData); +} diff --git a/java/src/com/zerotier/sdk/VirtualNetworkRoute.java b/java/src/com/zerotier/sdk/VirtualNetworkRoute.java new file mode 100644 index 0000000..b89dce7 --- /dev/null +++ b/java/src/com/zerotier/sdk/VirtualNetworkRoute.java @@ -0,0 +1,102 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +package com.zerotier.sdk; + +import java.net.InetSocketAddress; + +public final class VirtualNetworkRoute implements Comparable +{ + private VirtualNetworkRoute() { + target = null; + via = null; + flags = 0; + metric = 0; + } + + /** + * Target network / netmask bits (in port field) or NULL or 0.0.0.0/0 for default + */ + public InetSocketAddress target; + + /** + * Gateway IP address (port ignored) or NULL (family == 0) for LAN-local (no gateway) + */ + public InetSocketAddress via; + + /** + * Route flags + */ + public int flags; + + /** + * Route metric (not currently used) + */ + public int metric; + + + @Override + public int compareTo(VirtualNetworkRoute other) { + return target.toString().compareTo(other.target.toString()); + } + + public boolean equals(VirtualNetworkRoute other) { + boolean targetEquals; + if (target == null && other.target == null) { + targetEquals = true; + } + else if (target == null && other.target != null) { + targetEquals = false; + } + else if (target != null && other.target == null) { + targetEquals = false; + } + else { + targetEquals = target.equals(other.target); + } + + + boolean viaEquals; + if (via == null && other.via == null) { + viaEquals = true; + } + else if (via == null && other.via != null) { + viaEquals = false; + } + else if (via != null && other.via == null) { + viaEquals = false; + } + else { + viaEquals = via.equals(other.via); + } + + return viaEquals && + viaEquals && + flags == other.flags && + metric == other.metric; + } +} diff --git a/java/src/com/zerotier/sdk/VirtualNetworkStatus.java b/java/src/com/zerotier/sdk/VirtualNetworkStatus.java new file mode 100644 index 0000000..2d00561 --- /dev/null +++ b/java/src/com/zerotier/sdk/VirtualNetworkStatus.java @@ -0,0 +1,59 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ +package com.zerotier.sdk; + +public enum VirtualNetworkStatus { + /** + * Waiting for network configuration (also means revision == 0) + */ + NETWORK_STATUS_REQUESTING_CONFIGURATION, + + /** + * Configuration received and we are authorized + */ + NETWORK_STATUS_OK, + + /** + * Netconf master told us 'nope' + */ + NETWORK_STATUS_ACCESS_DENIED, + + /** + * Netconf master exists, but this virtual network does not + */ + NETWORK_STATUS_NOT_FOUND, + + /** + * Initialization of network failed or other internal error + */ + NETWORK_STATUS_PORT_ERROR, + + /** + * ZeroTier One version too old + */ + NETWORK_STATUS_CLIENT_TOO_OLD +} diff --git a/java/src/com/zerotier/sdk/VirtualNetworkType.java b/java/src/com/zerotier/sdk/VirtualNetworkType.java new file mode 100644 index 0000000..ab1f4e0 --- /dev/null +++ b/java/src/com/zerotier/sdk/VirtualNetworkType.java @@ -0,0 +1,39 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ +package com.zerotier.sdk; + +public enum VirtualNetworkType { + /** + * Private networks are authorized via certificates of membership + */ + NETWORK_TYPE_PRIVATE, + + /** + * Public networks have no access control -- they'll always be AUTHORIZED + */ + NETWORK_TYPE_PUBLIC +} diff --git a/macui/ZeroTier One.xcodeproj/project.pbxproj b/macui/ZeroTier One.xcodeproj/project.pbxproj new file mode 100644 index 0000000..fc5cfc1 --- /dev/null +++ b/macui/ZeroTier One.xcodeproj/project.pbxproj @@ -0,0 +1,382 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 932D472F1D1CD499004BCFE2 /* ZeroTierIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 932D472E1D1CD499004BCFE2 /* ZeroTierIcon.icns */; }; + 932D47331D1CD861004BCFE2 /* PreferencesViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 932D47311D1CD861004BCFE2 /* PreferencesViewController.xib */; }; + 932D47371D1CDC9B004BCFE2 /* AboutViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 932D47351D1CDC9B004BCFE2 /* AboutViewController.xib */; }; + 93326BDE1CE7C816005CA2AC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 93326BDD1CE7C816005CA2AC /* Assets.xcassets */; }; + 93326BE11CE7C816005CA2AC /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 93326BDF1CE7C816005CA2AC /* MainMenu.xib */; }; + 93326BEB1CE7D9B9005CA2AC /* JoinNetworkViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 93326BE91CE7D9B9005CA2AC /* JoinNetworkViewController.xib */; }; + 93326BEF1CE7DA30005CA2AC /* ShowNetworksViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 93326BED1CE7DA30005CA2AC /* ShowNetworksViewController.xib */; }; + 93D1675F1D54191C00330C99 /* NodeStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1675E1D54191C00330C99 /* NodeStatus.m */; }; + 93D167621D541BC200330C99 /* ServiceCom.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167611D541BC200330C99 /* ServiceCom.m */; }; + 93D167661D54308200330C99 /* Network.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167651D54308200330C99 /* Network.m */; }; + 93D167691D57E7EA00330C99 /* AboutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167681D57E7EA00330C99 /* AboutViewController.m */; }; + 93D1676D1D57EB8400330C99 /* PreferencesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1676C1D57EB8400330C99 /* PreferencesViewController.m */; }; + 93D167701D57FD3800330C99 /* NetworkMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1676F1D57FD3800330C99 /* NetworkMonitor.m */; }; + 93D167731D58093C00330C99 /* NetworkInfoCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167721D58093C00330C99 /* NetworkInfoCell.m */; }; + 93D167761D580C3500330C99 /* ShowNetworksViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167751D580C3500330C99 /* ShowNetworksViewController.m */; }; + 93D167791D5815E600330C99 /* JoinNetworkViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D167781D5815E600330C99 /* JoinNetworkViewController.m */; }; + 93D1677C1D58228A00330C99 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1677B1D58228A00330C99 /* AppDelegate.m */; }; + 93D1679B1D58300F00330C99 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D1679A1D58300F00330C99 /* main.m */; }; + 93D1679D1D595F0000330C99 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D1679C1D595F0000330C99 /* WebKit.framework */; }; + 93DAFB271D3F0BEE004D5417 /* about.html in Resources */ = {isa = PBXBuildFile; fileRef = 93DAFB261D3F0BEE004D5417 /* about.html */; }; + 93DAFE4B1CFE53CA00547CC4 /* AuthtokenCopy.m in Sources */ = {isa = PBXBuildFile; fileRef = 93DAFE4A1CFE53CA00547CC4 /* AuthtokenCopy.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 932D472E1D1CD499004BCFE2 /* ZeroTierIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = ZeroTierIcon.icns; sourceTree = ""; }; + 932D47311D1CD861004BCFE2 /* PreferencesViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PreferencesViewController.xib; sourceTree = ""; }; + 932D47351D1CDC9B004BCFE2 /* AboutViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AboutViewController.xib; sourceTree = ""; }; + 93326BD81CE7C816005CA2AC /* ZeroTier One.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ZeroTier One.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 93326BDD1CE7C816005CA2AC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 93326BE01CE7C816005CA2AC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 93326BE21CE7C816005CA2AC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 93326BE91CE7D9B9005CA2AC /* JoinNetworkViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JoinNetworkViewController.xib; sourceTree = ""; }; + 93326BED1CE7DA30005CA2AC /* ShowNetworksViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShowNetworksViewController.xib; sourceTree = ""; }; + 93D1675D1D54191C00330C99 /* NodeStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NodeStatus.h; sourceTree = ""; }; + 93D1675E1D54191C00330C99 /* NodeStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NodeStatus.m; sourceTree = ""; }; + 93D167601D541BC200330C99 /* ServiceCom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServiceCom.h; sourceTree = ""; }; + 93D167611D541BC200330C99 /* ServiceCom.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ServiceCom.m; sourceTree = ""; }; + 93D167641D54308200330C99 /* Network.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Network.h; sourceTree = ""; }; + 93D167651D54308200330C99 /* Network.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Network.m; sourceTree = ""; }; + 93D167671D57E7EA00330C99 /* AboutViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutViewController.h; sourceTree = ""; }; + 93D167681D57E7EA00330C99 /* AboutViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutViewController.m; sourceTree = ""; }; + 93D1676B1D57EB8400330C99 /* PreferencesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesViewController.h; sourceTree = ""; }; + 93D1676C1D57EB8400330C99 /* PreferencesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesViewController.m; sourceTree = ""; }; + 93D1676E1D57FD3800330C99 /* NetworkMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkMonitor.h; sourceTree = ""; }; + 93D1676F1D57FD3800330C99 /* NetworkMonitor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetworkMonitor.m; sourceTree = ""; }; + 93D167711D58093C00330C99 /* NetworkInfoCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkInfoCell.h; sourceTree = ""; }; + 93D167721D58093C00330C99 /* NetworkInfoCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetworkInfoCell.m; sourceTree = ""; }; + 93D167741D580C3500330C99 /* ShowNetworksViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShowNetworksViewController.h; sourceTree = ""; }; + 93D167751D580C3500330C99 /* ShowNetworksViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShowNetworksViewController.m; sourceTree = ""; }; + 93D167771D5815E600330C99 /* JoinNetworkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JoinNetworkViewController.h; sourceTree = ""; }; + 93D167781D5815E600330C99 /* JoinNetworkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JoinNetworkViewController.m; sourceTree = ""; }; + 93D1677A1D58228A00330C99 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 93D1677B1D58228A00330C99 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 93D1679A1D58300F00330C99 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 93D1679C1D595F0000330C99 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + 93DAFB261D3F0BEE004D5417 /* about.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = about.html; sourceTree = ""; }; + 93DAFE4A1CFE53CA00547CC4 /* AuthtokenCopy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AuthtokenCopy.m; sourceTree = ""; }; + 93DAFE4C1CFE53DA00547CC4 /* AuthtokenCopy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AuthtokenCopy.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 93326BD51CE7C816005CA2AC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 93D1679D1D595F0000330C99 /* WebKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 93326BCF1CE7C816005CA2AC = { + isa = PBXGroup; + children = ( + 93D1679C1D595F0000330C99 /* WebKit.framework */, + 93326BDA1CE7C816005CA2AC /* ZeroTier One */, + 93326BD91CE7C816005CA2AC /* Products */, + ); + sourceTree = ""; + }; + 93326BD91CE7C816005CA2AC /* Products */ = { + isa = PBXGroup; + children = ( + 93326BD81CE7C816005CA2AC /* ZeroTier One.app */, + ); + name = Products; + sourceTree = ""; + }; + 93326BDA1CE7C816005CA2AC /* ZeroTier One */ = { + isa = PBXGroup; + children = ( + 932D472E1D1CD499004BCFE2 /* ZeroTierIcon.icns */, + 93326BDD1CE7C816005CA2AC /* Assets.xcassets */, + 93326BDF1CE7C816005CA2AC /* MainMenu.xib */, + 93326BE21CE7C816005CA2AC /* Info.plist */, + 93DAFE4A1CFE53CA00547CC4 /* AuthtokenCopy.m */, + 93DAFE4C1CFE53DA00547CC4 /* AuthtokenCopy.h */, + 93D1676E1D57FD3800330C99 /* NetworkMonitor.h */, + 93D1676F1D57FD3800330C99 /* NetworkMonitor.m */, + 93DAFB261D3F0BEE004D5417 /* about.html */, + 93D1675D1D54191C00330C99 /* NodeStatus.h */, + 93D1675E1D54191C00330C99 /* NodeStatus.m */, + 93D167601D541BC200330C99 /* ServiceCom.h */, + 93D167611D541BC200330C99 /* ServiceCom.m */, + 93D167641D54308200330C99 /* Network.h */, + 93D167651D54308200330C99 /* Network.m */, + 93D167671D57E7EA00330C99 /* AboutViewController.h */, + 93D167681D57E7EA00330C99 /* AboutViewController.m */, + 932D47351D1CDC9B004BCFE2 /* AboutViewController.xib */, + 93D1676B1D57EB8400330C99 /* PreferencesViewController.h */, + 93D1676C1D57EB8400330C99 /* PreferencesViewController.m */, + 932D47311D1CD861004BCFE2 /* PreferencesViewController.xib */, + 93D167711D58093C00330C99 /* NetworkInfoCell.h */, + 93D167721D58093C00330C99 /* NetworkInfoCell.m */, + 93D167741D580C3500330C99 /* ShowNetworksViewController.h */, + 93D167751D580C3500330C99 /* ShowNetworksViewController.m */, + 93326BED1CE7DA30005CA2AC /* ShowNetworksViewController.xib */, + 93D167771D5815E600330C99 /* JoinNetworkViewController.h */, + 93D167781D5815E600330C99 /* JoinNetworkViewController.m */, + 93326BE91CE7D9B9005CA2AC /* JoinNetworkViewController.xib */, + 93D1677A1D58228A00330C99 /* AppDelegate.h */, + 93D1677B1D58228A00330C99 /* AppDelegate.m */, + 93D1679A1D58300F00330C99 /* main.m */, + ); + path = "ZeroTier One"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 93326BD71CE7C816005CA2AC /* ZeroTier One */ = { + isa = PBXNativeTarget; + buildConfigurationList = 93326BE51CE7C816005CA2AC /* Build configuration list for PBXNativeTarget "ZeroTier One" */; + buildPhases = ( + 93326BD41CE7C816005CA2AC /* Sources */, + 93326BD51CE7C816005CA2AC /* Frameworks */, + 93326BD61CE7C816005CA2AC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ZeroTier One"; + productName = "ZeroTier One"; + productReference = 93326BD81CE7C816005CA2AC /* ZeroTier One.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 93326BD01CE7C816005CA2AC /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0730; + LastUpgradeCheck = 0800; + ORGANIZATIONNAME = "ZeroTier, Inc"; + TargetAttributes = { + 93326BD71CE7C816005CA2AC = { + CreatedOnToolsVersion = 7.3; + }; + }; + }; + buildConfigurationList = 93326BD31CE7C816005CA2AC /* Build configuration list for PBXProject "ZeroTier One" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 93326BCF1CE7C816005CA2AC; + productRefGroup = 93326BD91CE7C816005CA2AC /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 93326BD71CE7C816005CA2AC /* ZeroTier One */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 93326BD61CE7C816005CA2AC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 93DAFB271D3F0BEE004D5417 /* about.html in Resources */, + 93326BEF1CE7DA30005CA2AC /* ShowNetworksViewController.xib in Resources */, + 932D47371D1CDC9B004BCFE2 /* AboutViewController.xib in Resources */, + 93326BEB1CE7D9B9005CA2AC /* JoinNetworkViewController.xib in Resources */, + 93326BDE1CE7C816005CA2AC /* Assets.xcassets in Resources */, + 93326BE11CE7C816005CA2AC /* MainMenu.xib in Resources */, + 932D472F1D1CD499004BCFE2 /* ZeroTierIcon.icns in Resources */, + 932D47331D1CD861004BCFE2 /* PreferencesViewController.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 93326BD41CE7C816005CA2AC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 93D1679B1D58300F00330C99 /* main.m in Sources */, + 93D167621D541BC200330C99 /* ServiceCom.m in Sources */, + 93D167761D580C3500330C99 /* ShowNetworksViewController.m in Sources */, + 93DAFE4B1CFE53CA00547CC4 /* AuthtokenCopy.m in Sources */, + 93D167701D57FD3800330C99 /* NetworkMonitor.m in Sources */, + 93D1675F1D54191C00330C99 /* NodeStatus.m in Sources */, + 93D167691D57E7EA00330C99 /* AboutViewController.m in Sources */, + 93D1676D1D57EB8400330C99 /* PreferencesViewController.m in Sources */, + 93D1677C1D58228A00330C99 /* AppDelegate.m in Sources */, + 93D167731D58093C00330C99 /* NetworkInfoCell.m in Sources */, + 93D167661D54308200330C99 /* Network.m in Sources */, + 93D167791D5815E600330C99 /* JoinNetworkViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 93326BDF1CE7C816005CA2AC /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 93326BE01CE7C816005CA2AC /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 93326BE31CE7C816005CA2AC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 93326BE41CE7C816005CA2AC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + 93326BE61CE7C816005CA2AC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "ZeroTier One/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = "com.zerotier.ZeroTier-One"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "ZeroTier One/ZeroTier One-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 93326BE71CE7C816005CA2AC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "ZeroTier One/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = "com.zerotier.ZeroTier-One"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "ZeroTier One/ZeroTier One-Bridging-Header.h"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 93326BD31CE7C816005CA2AC /* Build configuration list for PBXProject "ZeroTier One" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 93326BE31CE7C816005CA2AC /* Debug */, + 93326BE41CE7C816005CA2AC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 93326BE51CE7C816005CA2AC /* Build configuration list for PBXNativeTarget "ZeroTier One" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 93326BE61CE7C816005CA2AC /* Debug */, + 93326BE71CE7C816005CA2AC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 93326BD01CE7C816005CA2AC /* Project object */; +} diff --git a/macui/ZeroTier One.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/macui/ZeroTier One.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..fd60338 --- /dev/null +++ b/macui/ZeroTier One.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/macui/ZeroTier One/AboutViewController.h b/macui/ZeroTier One/AboutViewController.h new file mode 100644 index 0000000..d3d5bc1 --- /dev/null +++ b/macui/ZeroTier One/AboutViewController.h @@ -0,0 +1,33 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import +#import + +@interface AboutViewController : NSViewController + +@property (nonatomic, weak) IBOutlet WebView *webView; + +- (void)viewDidLoad; + +- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation + request:(NSURLRequest *)request + frame:(WebFrame *)frame +decisionListener:(id)listener; + +@end diff --git a/macui/ZeroTier One/AboutViewController.m b/macui/ZeroTier One/AboutViewController.m new file mode 100644 index 0000000..7f92977 --- /dev/null +++ b/macui/ZeroTier One/AboutViewController.m @@ -0,0 +1,57 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import "AboutViewController.h" + +@interface AboutViewController () + +@end + +@implementation AboutViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self.webView setWantsLayer:YES]; + self.webView.layer.borderWidth = 1.0f; + [self.webView.layer setCornerRadius:1.0f]; + self.webView.layer.masksToBounds = YES; + [self.webView.layer setBorderColor:[[NSColor darkGrayColor] CGColor]]; + self.webView.policyDelegate = self; + + NSBundle *bundle = [NSBundle mainBundle]; + NSURL *path = [bundle URLForResource:@"about" withExtension:@"html"]; + if(path) { + [self.webView.mainFrame loadRequest:[NSURLRequest requestWithURL:path]]; + } +} + +- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation + request:(NSURLRequest *)request + frame:(WebFrame *)frame +decisionListener:(id)listener +{ + if(request.URL != nil && request.URL.host != nil) { + [[NSWorkspace sharedWorkspace] openURL:request.URL]; + } + else { + [listener use]; + } +} + +@end diff --git a/macui/ZeroTier One/AboutViewController.xib b/macui/ZeroTier One/AboutViewController.xib new file mode 100644 index 0000000..a0df0fc --- /dev/null +++ b/macui/ZeroTier One/AboutViewController.xib @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macui/ZeroTier One/AppDelegate.h b/macui/ZeroTier One/AppDelegate.h new file mode 100644 index 0000000..a00cfba --- /dev/null +++ b/macui/ZeroTier One/AppDelegate.h @@ -0,0 +1,61 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import + +@class NetworkMonitor; +@class Network; +@class NodeStatus; + +@interface AppDelegate : NSObject + +@property (weak, nonatomic) IBOutlet NSWindow *window; + +@property (nonatomic) NSStatusItem *statusItem; + +@property (nonatomic) NSPopover *networkListPopover; +@property (nonatomic) NSPopover *joinNetworkPopover; +@property (nonatomic) NSPopover *preferencesPopover; +@property (nonatomic) NSPopover *aboutPopover; + +@property (nonatomic) id transientMonitor; + +@property (nonatomic) NetworkMonitor *monitor; + +@property (nonatomic) NSMutableArray *networks; + +@property (nonatomic) NodeStatus *status; + +- (void)buildMenu; + +- (void)onNetworkListUpdated:(NSNotification*)note; +- (void)onNodeStatusUpdated:(NSNotification*)note; + +- (void)showNetworks; +- (void)joinNetwork; +- (void)showPreferences; +- (void)showAbout; +- (void)quit; +- (void)toggleNetwork:(NSMenuItem*)sender; +- (void)copyNodeID; + +- (void)closeJoinNetworkPopover; + +- (void)darkModeChanged:(NSNotification*)note; + +@end diff --git a/macui/ZeroTier One/AppDelegate.m b/macui/ZeroTier One/AppDelegate.m new file mode 100644 index 0000000..ae3e042 --- /dev/null +++ b/macui/ZeroTier One/AppDelegate.m @@ -0,0 +1,378 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import "AppDelegate.h" +#import "NetworkMonitor.h" +#import "Network.h" +#import "NodeStatus.h" +#import "JoinNetworkViewController.h" +#import "ShowNetworksViewController.h" +#import "PreferencesViewController.h" +#import "AboutViewController.h" +#import "ServiceCom.h" + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:-2.0f]; + self.networkListPopover = [[NSPopover alloc] init]; + self.joinNetworkPopover = [[NSPopover alloc] init]; + self.preferencesPopover = [[NSPopover alloc] init]; + self.aboutPopover = [[NSPopover alloc] init]; + self.transientMonitor = nil; + self.monitor = [[NetworkMonitor alloc] init]; + self.networks = [NSMutableArray array]; + self.status = nil; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSDictionary *defaultsDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:@"firstRun"]; + [defaults registerDefaults:defaultsDict]; + + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + + [nc addObserver:self + selector:@selector(onNetworkListUpdated:) + name:NetworkUpdateKey + object:nil]; + [nc addObserver:self + selector:@selector(onNodeStatusUpdated:) + name:StatusUpdateKey + object:nil]; + + NSString *osxMode = [defaults stringForKey:@"AppleInterfaceStyle"]; + + if(osxMode != nil && [osxMode isEqualToString:@"Dark"]) { + self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMacWhite"]; + } + else { + self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMac"]; + } + + [[NSDistributedNotificationCenter defaultCenter] addObserver:self + selector:@selector(darkModeChanged:) + name:@"AppleInterfaceThemeChangedNotification" + object:nil]; + + [self buildMenu]; + JoinNetworkViewController *jnvc = [[JoinNetworkViewController alloc] initWithNibName:@"JoinNetworkViewController" bundle:nil]; + jnvc.appDelegate = self; + self.joinNetworkPopover.contentViewController = jnvc; + self.joinNetworkPopover.behavior = NSPopoverBehaviorTransient; + + ShowNetworksViewController *showNetworksView = [[ShowNetworksViewController alloc] initWithNibName:@"ShowNetworksViewController" bundle:nil]; + showNetworksView.netMonitor = self.monitor; + self.networkListPopover.contentViewController = showNetworksView; + self.networkListPopover.behavior = NSPopoverBehaviorTransient; + + PreferencesViewController *prefsView = [[PreferencesViewController alloc] initWithNibName:@"PreferencesViewController" bundle:nil]; + self.preferencesPopover.contentViewController = prefsView; + self.preferencesPopover.behavior = NSPopoverBehaviorTransient; + + self.aboutPopover.contentViewController = [[AboutViewController alloc] initWithNibName:@"AboutViewController" bundle:nil]; + self.aboutPopover.behavior = NSPopoverBehaviorTransient; + + BOOL firstRun = [defaults boolForKey:@"firstRun"]; + + if(firstRun) { + [defaults setBool:NO forKey:@"firstRun"]; + [defaults synchronize]; + + [prefsView setLaunchAtLoginEnabled:YES]; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + sleep(2); + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [self showAbout]; + }]; + }); + } + + [self.monitor updateNetworkInfo]; + [self.monitor start]; +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self + name:@"AppleInterfaceThemeChangedNotification" + object:nil]; +} + +- (void)showNetworks { + NSButton *button = nil; + NSRect frame; + if ([self.statusItem respondsToSelector:@selector(button)]) { + button = self.statusItem.button; + frame = button.bounds; + } else if ([self.statusItem respondsToSelector:@selector(_button)]) { + button = [self.statusItem performSelector:@selector(_button)]; + frame = button.bounds; + } else { + NSLog(@"Can't get view. Uh oh."); + return; + } + + [self.networkListPopover showRelativeToRect:frame + ofView:button + preferredEdge:NSMinYEdge]; + + if(self.transientMonitor == nil) { + self.transientMonitor = + [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown) + handler:^(NSEvent * _Nonnull e) { + [NSEvent removeMonitor:self.transientMonitor]; + self.transientMonitor = nil; + [self.networkListPopover close]; + }]; + } +} + +- (void)joinNetwork { + NSButton *button = nil; + NSRect frame; + if ([self.statusItem respondsToSelector:@selector(button)]) { + button = self.statusItem.button; + frame = button.bounds; + } else if ([self.statusItem respondsToSelector:@selector(_button)]) { + button = [self.statusItem performSelector:@selector(_button)]; + frame = button.bounds; + } else { + NSLog(@"Can't get view. Uh oh."); + return; + } + + [self.joinNetworkPopover showRelativeToRect:button.bounds + ofView:button + preferredEdge:NSMinYEdge]; + if(self.transientMonitor == nil) { + self.transientMonitor = + [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown) + handler:^(NSEvent * _Nonnull e) { + [NSEvent removeMonitor:self.transientMonitor]; + self.transientMonitor = nil; + [self.joinNetworkPopover close]; + }]; + } +} + +- (void)showPreferences { + NSButton *button = nil; + NSRect frame; + if ([self.statusItem respondsToSelector:@selector(button)]) { + button = self.statusItem.button; + frame = button.bounds; + } else if ([self.statusItem respondsToSelector:@selector(_button)]) { + button = [self.statusItem performSelector:@selector(_button)]; + frame = button.bounds; + } else { + NSLog(@"Can't get view. Uh oh."); + return; + } + + [self.preferencesPopover showRelativeToRect:button.bounds + ofView:button + preferredEdge:NSMinYEdge]; + if(self.transientMonitor == nil) { + [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown) + handler:^(NSEvent * _Nonnull e) { + [NSEvent removeMonitor:self.transientMonitor]; + self.transientMonitor = nil; + [self.preferencesPopover close]; + }]; + } +} + +- (void)showAbout { + NSButton *button = nil; + NSRect frame; + if ([self.statusItem respondsToSelector:@selector(button)]) { + button = self.statusItem.button; + frame = button.bounds; + } else if ([self.statusItem respondsToSelector:@selector(_button)]) { + button = [self.statusItem performSelector:@selector(_button)]; + frame = button.bounds; + } else { + NSLog(@"Can't get view. Uh oh."); + return; + } + + [self.aboutPopover showRelativeToRect:button.bounds + ofView:button + preferredEdge:NSMinYEdge]; + if(self.transientMonitor == nil) { + [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown) + handler:^(NSEvent * _Nonnull e) { + [NSEvent removeMonitor:self.transientMonitor]; + self.transientMonitor = nil; + [self.aboutPopover close]; + }]; + } +} + +- (void)quit { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; +} + +- (void)onNetworkListUpdated:(NSNotification*)note { + NSArray *netList = [note.userInfo objectForKey:@"networks"]; + [(ShowNetworksViewController*)self.networkListPopover.contentViewController setNetworks:netList]; + self.networks = [netList mutableCopy]; + + [self buildMenu]; +} + +- (void)onNodeStatusUpdated:(NSNotification*)note { + NodeStatus *status = [note.userInfo objectForKey:@"status"]; + self.status = status; + + [self buildMenu]; +} + +- (void)buildMenu { + NSMenu *menu = [[NSMenu alloc] init]; + + if(self.status != nil) { + NSString *nodeId = @"Node ID: "; + nodeId = [nodeId stringByAppendingString:self.status.address]; + [menu addItem:[[NSMenuItem alloc] initWithTitle:nodeId + action:@selector(copyNodeID) + keyEquivalent:@""]]; + [menu addItem:[NSMenuItem separatorItem]]; + } + + [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Network Details..." + action:@selector(showNetworks) + keyEquivalent:@"n"]]; + [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Join Network..." + action:@selector(joinNetwork) + keyEquivalent:@"j"]]; + + [menu addItem:[NSMenuItem separatorItem]]; + + if([self.networks count] > 0) { + for(Network *net in self.networks) { + NSString *nwid = [NSString stringWithFormat:@"%10llx", net.nwid]; + NSString *networkName = @""; + if([net.name lengthOfBytesUsingEncoding:NSUTF8StringEncoding] == 0) { + networkName = nwid; + } + else { + networkName = [NSString stringWithFormat:@"%@ (%@)", nwid, net.name]; + } + + if(net.allowDefault && net.connected) { + networkName = [networkName stringByAppendingString:@" [default]"]; + } + + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:networkName + action:@selector(toggleNetwork:) + keyEquivalent:@""]; + if(net.connected) { + item.state = NSOnState; + } + else { + item.state = NSOffState; + } + + item.representedObject = net; + + [menu addItem:item]; + } + + [menu addItem:[NSMenuItem separatorItem]]; + } + + [menu addItem:[[NSMenuItem alloc] initWithTitle:@"About ZeroTier One..." + action:@selector(showAbout) + keyEquivalent:@""]]; + [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Preferences..." + action:@selector(showPreferences) + keyEquivalent:@""]]; + + [menu addItem:[NSMenuItem separatorItem]]; + + [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Quit" + action:@selector(quit) + keyEquivalent:@"q"]]; + + self.statusItem.menu = menu; +} + +- (void)toggleNetwork:(NSMenuItem*)sender { + Network *network = sender.representedObject; + NSString *nwid = [NSString stringWithFormat:@"%10llx", network.nwid]; + + if(network.connected) { + NSError *error = nil; + + [[ServiceCom sharedInstance] leaveNetwork:nwid error:&error]; + + if (error) { + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Ok"]; + + [alert runModal]; + } + } + else { + NSError *error = nil; + [[ServiceCom sharedInstance] joinNetwork:nwid + allowManaged:network.allowManaged + allowGlobal:network.allowGlobal + allowDefault:(network.allowDefault && ![Network defaultRouteExists:self.networks]) + error:&error]; + + if (error) { + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Ok"]; + + [alert runModal]; + } + } +} + +- (void)copyNodeID { + if(self.status != nil) { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard declareTypes:[NSArray arrayWithObject:NSPasteboardTypeString] owner:nil]; + [pasteboard setString:self.status.address forType:NSPasteboardTypeString]; + } +} + +- (void)darkModeChanged:(NSNotification*)note { + NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"]; + + if(osxMode != nil && [osxMode isEqualToString:@"Dark"]) { + self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMacWhite"]; + } + else { + self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMac"]; + } +} + +- (void)closeJoinNetworkPopover { + if (self.transientMonitor) { + [NSEvent removeMonitor:self.transientMonitor]; + self.transientMonitor = nil; + } + [self.joinNetworkPopover close]; +} + +@end diff --git a/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/Contents.json b/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..24c81d3 --- /dev/null +++ b/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,59 @@ +{ + "images" : [ + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "ZeroTierIcon512x512.png", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/ZeroTierIcon512x512.png b/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/ZeroTierIcon512x512.png new file mode 100644 index 0000000..d225c2e Binary files /dev/null and b/macui/ZeroTier One/Assets.xcassets/AppIcon.appiconset/ZeroTierIcon512x512.png differ diff --git a/macui/ZeroTier One/Assets.xcassets/Contents.json b/macui/ZeroTier One/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/macui/ZeroTier One/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Contents.json b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Contents.json new file mode 100644 index 0000000..a680b58 --- /dev/null +++ b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "mac", + "filename" : "Menubar.png", + "scale" : "1x" + }, + { + "idiom" : "mac", + "filename" : "MenuBar@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/MenuBar@2x.png b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/MenuBar@2x.png new file mode 100644 index 0000000..9fd3d3d Binary files /dev/null and b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/MenuBar@2x.png differ diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Menubar.png b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Menubar.png new file mode 100644 index 0000000..ee0d7e3 Binary files /dev/null and b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMac.imageset/Menubar.png differ diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/Contents.json b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/Contents.json new file mode 100644 index 0000000..6173776 --- /dev/null +++ b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "mac", + "filename" : "MenubarWhite.png", + "scale" : "1x" + }, + { + "idiom" : "mac", + "filename" : "MenubarWhite@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite.png b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite.png new file mode 100644 index 0000000..7049ae5 Binary files /dev/null and b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite.png differ diff --git a/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite@2x.png b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite@2x.png new file mode 100644 index 0000000..8c20e36 Binary files /dev/null and b/macui/ZeroTier One/Assets.xcassets/MenuBarIconMacWhite.imageset/MenubarWhite@2x.png differ diff --git a/macui/ZeroTier One/AuthtokenCopy.h b/macui/ZeroTier One/AuthtokenCopy.h new file mode 100644 index 0000000..f0497cc --- /dev/null +++ b/macui/ZeroTier One/AuthtokenCopy.h @@ -0,0 +1,26 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef AuthtokenCopy_h +#define AuthtokenCopy_h + +#import + +NSString* getAdminAuthToken(AuthorizationRef authRef); + +#endif /* AuthtokenCopy_h */ diff --git a/macui/ZeroTier One/AuthtokenCopy.m b/macui/ZeroTier One/AuthtokenCopy.m new file mode 100644 index 0000000..a10350f --- /dev/null +++ b/macui/ZeroTier One/AuthtokenCopy.m @@ -0,0 +1,97 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import + +#import "AuthtokenCopy.h" + + +NSString* getAdminAuthToken(AuthorizationRef authRef) { + char *tool = "/bin/cat"; + char *args[] = { "/Library/Application Support/ZeroTier/One/authtoken.secret", NULL}; + FILE *pipe = nil; + char token[25]; + memset(token, 0, sizeof(char)*25); + + + OSStatus status = AuthorizationExecuteWithPrivileges(authRef, tool, kAuthorizationFlagDefaults, args, &pipe); + + if (status != errAuthorizationSuccess) { + NSLog(@"Reading authtoken failed!"); + + + switch(status) { + case errAuthorizationDenied: + NSLog(@"Autorization Denied"); + break; + case errAuthorizationCanceled: + NSLog(@"Authorization Canceled"); + break; + case errAuthorizationInternal: + NSLog(@"Authorization Internal"); + break; + case errAuthorizationBadAddress: + NSLog(@"Bad Address"); + break; + case errAuthorizationInvalidRef: + NSLog(@"Invalid Ref"); + break; + case errAuthorizationInvalidSet: + NSLog(@"Invalid Set"); + break; + case errAuthorizationInvalidTag: + NSLog(@"Invalid Tag"); + break; + case errAuthorizationInvalidFlags: + NSLog(@"Invalid Flags"); + break; + case errAuthorizationInvalidPointer: + NSLog(@"Invalid Pointer"); + break; + case errAuthorizationToolExecuteFailure: + NSLog(@"Tool Execute Failure"); + break; + case errAuthorizationToolEnvironmentError: + NSLog(@"Tool Environment Failure"); + break; + case errAuthorizationExternalizeNotAllowed: + NSLog(@"Externalize Not Allowed"); + break; + case errAuthorizationInteractionNotAllowed: + NSLog(@"Interaction Not Allowed"); + break; + case errAuthorizationInternalizeNotAllowed: + NSLog(@"Internalize Not Allowed"); + break; + default: + NSLog(@"Unknown Error"); + break; + } + + return @""; + } + + if(pipe != nil) { + fread(&token, sizeof(char), 24, pipe); + fclose(pipe); + + return [NSString stringWithUTF8String:token]; + } + + return @""; +} diff --git a/macui/ZeroTier One/Base.lproj/MainMenu.xib b/macui/ZeroTier One/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..6b6da2a --- /dev/null +++ b/macui/ZeroTier One/Base.lproj/MainMenu.xib @@ -0,0 +1,680 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macui/ZeroTier One/Info.plist b/macui/ZeroTier One/Info.plist new file mode 100644 index 0000000..e04b5f2 --- /dev/null +++ b/macui/ZeroTier One/Info.plist @@ -0,0 +1,41 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + ZeroTierIcon.icns + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 15 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + LSUIElement + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSHumanReadableCopyright + Copyright © 2016 ZeroTier, Inc. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macui/ZeroTier One/JoinNetworkViewController.h b/macui/ZeroTier One/JoinNetworkViewController.h new file mode 100644 index 0000000..428959f --- /dev/null +++ b/macui/ZeroTier One/JoinNetworkViewController.h @@ -0,0 +1,40 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import + + +extern NSString * const JoinedNetworksKey; + +@class AppDelegate; + +@interface JoinNetworkViewController : NSViewController + +@property (nonatomic, weak) IBOutlet NSComboBox *network; +@property (nonatomic, weak) IBOutlet NSButton *joinButton; +@property (nonatomic, weak) IBOutlet NSButton *allowManagedCheckBox; +@property (nonatomic, weak) IBOutlet NSButton *allowGlobalCheckBox; +@property (nonatomic, weak) IBOutlet NSButton *allowDefaultCheckBox; +@property (nonatomic, weak) IBOutlet AppDelegate *appDelegate; + +@property (nonatomic) NSMutableArray *values; + +- (IBAction)onJoinClicked:(id)sender; + + +@end diff --git a/macui/ZeroTier One/JoinNetworkViewController.m b/macui/ZeroTier One/JoinNetworkViewController.m new file mode 100644 index 0000000..cae2654 --- /dev/null +++ b/macui/ZeroTier One/JoinNetworkViewController.m @@ -0,0 +1,184 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import "JoinNetworkViewController.h" +#import "ServiceCom.h" +#import "AppDelegate.h" + + +NSString * const JoinedNetworksKey = @"com.zerotier.one.joined-networks"; + +@interface NSString (extra) + +- (BOOL)contains:(NSString*)find; + +@end + +@implementation NSString (extra) + +- (BOOL)contains:(NSString*)find { + NSRange range = [self rangeOfString:find]; + return range.location != NSNotFound; +} + +@end + + +@implementation JoinNetworkViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do view setup here. + [self.network setDelegate:self]; + [self.network setDataSource:self]; +} + +- (void)viewWillAppear { + [super viewWillAppear]; + + self.allowManagedCheckBox.state = NSOnState; + self.allowGlobalCheckBox.state = NSOffState; + self.allowDefaultCheckBox.state = NSOffState; + + self.network.stringValue = @""; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + NSMutableArray *vals = [[defaults stringArrayForKey:JoinedNetworksKey] mutableCopy]; + + if(vals) { + self.values = vals; + } +} + +- (void)viewWillDisappear { + [super viewWillDisappear]; + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + + [defaults setObject:self.values forKey:JoinedNetworksKey]; +} + +- (IBAction)onJoinClicked:(id)sender { + NSString *networkId = self.network.stringValue; + + NSError *error = nil; + [[ServiceCom sharedInstance] joinNetwork:networkId + allowManaged:(self.allowManagedCheckBox.state == NSOnState) + allowGlobal:(self.allowGlobalCheckBox.state == NSOnState) + allowDefault:(self.allowDefaultCheckBox.state == NSOnState) + error:&error]; + + if(error) { + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Ok"]; + + [alert runModal]; + return; + } + + self.network.stringValue = @""; + + if(![self.values containsObject:networkId]) { + [self.values insertObject:networkId atIndex:0]; + + while([self.values count] > 20) { + [self.values removeLastObject]; + } + } + + [self.appDelegate closeJoinNetworkPopover]; +} + +// NSComboBoxDelegate methods + +- (void)controlTextDidChange:(NSNotification *)obj { + NSComboBox *cb = (NSComboBox*)obj.object; + NSString *value = cb.stringValue; + + NSString *allowedCharacters = @"abcdefABCDEF0123456789"; + + NSString *outValue = @""; + + for(int i = 0; i < [value length]; ++i) { + if(![allowedCharacters contains:[NSString stringWithFormat:@"%C", [value characterAtIndex:i]]]) { + NSBeep(); + } + else { + outValue = [outValue stringByAppendingString:[NSString stringWithFormat:@"%C", [value characterAtIndex:i]]]; + } + } + + if([outValue lengthOfBytesUsingEncoding:NSUTF8StringEncoding] == 16) { + self.joinButton.enabled = YES; + } + else { + if([outValue lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > 16) { + NSRange range = {0, 16}; + range = [outValue rangeOfComposedCharacterSequencesForRange:range]; + outValue = [outValue substringWithRange:range]; + NSBeep(); + self.joinButton.enabled = YES; + } + else { + self.joinButton.enabled = NO; + } + } + + cb.stringValue = outValue; +} + +// end NSComboBoxDelegate methods + +// NSComboBoxDataSource methods + +- (NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox { + return [self.values count]; +} + +- (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)index { + return [self.values objectAtIndex:index]; +} + +- (NSUInteger)comboBox:(NSComboBox *)aComboBox indexOfItemWithStringValue:(NSString *)string { + NSUInteger counter = 0; + + for(NSString *val in self.values) { + if([val isEqualToString:string]) { + return counter; + } + + counter += 1; + } + + return NSNotFound; +} + +- (NSString*)comboBox:(NSComboBox *)aComboBox completedString:(NSString *)string { + for(NSString *val in self.values) { + if([val hasPrefix:string]) { + return val; + } + } + return nil; +} + +// end NSComboBoxDataSource methods + +@end diff --git a/macui/ZeroTier One/JoinNetworkViewController.xib b/macui/ZeroTier One/JoinNetworkViewController.xib new file mode 100644 index 0000000..5916109 --- /dev/null +++ b/macui/ZeroTier One/JoinNetworkViewController.xib @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macui/ZeroTier One/Network.h b/macui/ZeroTier One/Network.h new file mode 100644 index 0000000..957ff8d --- /dev/null +++ b/macui/ZeroTier One/Network.h @@ -0,0 +1,68 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import + +enum NetworkStatus { + REQUESTING_CONFIGURATION, + OK, + ACCESS_DENIED, + NOT_FOUND, + PORT_ERROR, + CLIENT_TOO_OLD, +}; + +enum NetworkType { + PUBLIC, + PRIVATE, +}; + +@interface Network : NSObject + +@property (readonly) NSArray *assignedAddresses; +@property (readonly) BOOL bridge; +@property (readonly) BOOL broadcastEnabled; +@property (readonly) BOOL dhcp; +@property (readonly) NSString *mac; +@property (readonly) int mtu; +@property (readonly) int netconfRevision; +@property (readonly) NSString *name; +@property (readonly) UInt64 nwid; +@property (readonly) NSString *portDeviceName; +@property (readonly) int portError; +@property (readonly) enum NetworkStatus status; +@property (readonly) enum NetworkType type; +@property (readonly) BOOL allowManaged; +@property (readonly) BOOL allowGlobal; +@property (readonly) BOOL allowDefault; +@property (readonly) BOOL connected; // not persisted. set to YES if loaded via json + +- (id)initWithJsonData:(NSDictionary*)jsonData; +- (id)initWithCoder:(NSCoder *)aDecoder; +- (void)encodeWithCoder:(NSCoder *)aCoder; ++ (BOOL)defaultRouteExists:(NSArray*)netList; +- (NSString*)statusString; +- (NSString*)typeString; + +- (BOOL)hasSameNetworkId:(UInt64)networkId; + +- (BOOL)isEqualToNetwork:(Network*)network; +- (BOOL)isEqual:(id)object; +- (NSUInteger)hash; + +@end diff --git a/macui/ZeroTier One/Network.m b/macui/ZeroTier One/Network.m new file mode 100644 index 0000000..8474aca --- /dev/null +++ b/macui/ZeroTier One/Network.m @@ -0,0 +1,337 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import "Network.h" + +NSString *NetworkAddressesKey = @"addresses"; +NSString *NetworkBridgeKey = @"bridge"; +NSString *NetworkBroadcastKey = @"broadcast"; +NSString *NetworkDhcpKey = @"dhcp"; +NSString *NetworkMacKey = @"mac"; +NSString *NetworkMtuKey = @"mtu"; +NSString *NetworkMulticastKey = @"multicast"; +NSString *NetworkNameKey = @"name"; +NSString *NetworkNetconfKey = @"netconf"; +NSString *NetworkNwidKey = @"nwid"; +NSString *NetworkPortNameKey = @"port"; +NSString *NetworkPortErrorKey = @"portError"; +NSString *NetworkStatusKey = @"status"; +NSString *NetworkTypeKey = @"type"; +NSString *NetworkAllowManagedKey = @"allowManaged"; +NSString *NetworkAllowGlobalKey = @"allowGlobal"; +NSString *NetworkAllowDefaultKey = @"allowDefault"; + +@implementation Network + +- (id)initWithJsonData:(NSDictionary*)jsonData +{ + self = [super init]; + + if(self) { + if([jsonData objectForKey:@"assignedAddresses"]) { + _assignedAddresses = (NSArray*)[jsonData objectForKey:@"assignedAddresses"]; + } + + if([jsonData objectForKey:@"bridge"]) { + _bridge = [(NSNumber*)[jsonData objectForKey:@"bridge"] boolValue]; + } + + if([jsonData objectForKey:@"broadcastEnabled"]) { + _broadcastEnabled = [(NSNumber*)[jsonData objectForKey:@"broadcastEnabled"] boolValue]; + } + + if([jsonData objectForKey:@"dhcp"]) { + _dhcp = [(NSNumber*)[jsonData objectForKey:@"dhcp"] boolValue]; + } + + if([jsonData objectForKey:@"mac"]) { + _mac = (NSString*)[jsonData objectForKey:@"mac"]; + } + + if([jsonData objectForKey:@"mtu"]) { + _mtu = [(NSNumber*)[jsonData objectForKey:@"mtu"] intValue]; + } + + if([jsonData objectForKey:@"name"]) { + _name = (NSString*)[jsonData objectForKey:@"name"]; + } + + if([jsonData objectForKey:@"netconfRevision"]) { + _netconfRevision = [(NSNumber*)[jsonData objectForKey:@"netconfRevision"] intValue]; + } + + if([jsonData objectForKey:@"nwid"]) { + NSString *networkid = (NSString*)[jsonData objectForKey:@"nwid"]; + + NSScanner *scanner = [NSScanner scannerWithString:networkid]; + [scanner scanHexLongLong:&_nwid]; + } + + if([jsonData objectForKey:@"portDeviceName"]) { + _portDeviceName = (NSString*)[jsonData objectForKey:@"portDeviceName"]; + } + + if([jsonData objectForKey:@"portError"]) { + _portError = [(NSNumber*)[jsonData objectForKey:@"portError"] intValue]; + } + + if([jsonData objectForKey:@"allowManaged"]) { + _allowManaged = [(NSNumber*)[jsonData objectForKey:@"allowManaged"] boolValue]; + } + + if([jsonData objectForKey:@"allowGlobal"]) { + _allowGlobal = [(NSNumber*)[jsonData objectForKey:@"allowGlobal"] boolValue]; + } + + if([jsonData objectForKey:@"allowDefault"]) { + _allowDefault = [(NSNumber*)[jsonData objectForKey:@"allowDefault"] boolValue]; + } + + if([jsonData objectForKey:@"status"]) { + NSString *statusStr = (NSString*)[jsonData objectForKey:@"status"]; + if([statusStr isEqualToString:@"REQUESTING_CONFIGURATION"]) { + _status = REQUESTING_CONFIGURATION; + } + else if([statusStr isEqualToString:@"OK"]) { + _status = OK; + } + else if([statusStr isEqualToString:@"ACCESS_DENIED"]) { + _status = ACCESS_DENIED; + } + else if([statusStr isEqualToString:@"NOT_FOUND"]) { + _status = NOT_FOUND; + } + else if([statusStr isEqualToString:@"PORT_ERROR"]) { + _status = PORT_ERROR; + } + else if([statusStr isEqualToString:@"CLIENT_TOO_OLD"]) { + _status = CLIENT_TOO_OLD; + } + } + + if([jsonData objectForKey:@"type"]) { + NSString *typeStr = (NSString*)[jsonData objectForKey:@"type"]; + if([typeStr isEqualToString:@"PRIVATE"]) { + _type = PRIVATE; + } + else if([typeStr isEqualToString:@"PUBLIC"]) { + _type = PUBLIC; + } + } + + _connected = YES; + } + + return self; +} +- (id)initWithCoder:(NSCoder *)aDecoder +{ + self = [super init]; + + if(self) { + if([aDecoder containsValueForKey:NetworkAddressesKey]) { + _assignedAddresses = (NSArray*)[aDecoder decodeObjectForKey:NetworkAddressesKey]; + } + + if([aDecoder containsValueForKey:NetworkBridgeKey]) { + _bridge = [aDecoder decodeBoolForKey:NetworkBridgeKey]; + } + + if([aDecoder containsValueForKey:NetworkBroadcastKey]) { + _broadcastEnabled = [aDecoder decodeBoolForKey:NetworkBroadcastKey]; + } + + if([aDecoder containsValueForKey:NetworkDhcpKey]) { + _dhcp = [aDecoder decodeBoolForKey:NetworkDhcpKey]; + } + + if([aDecoder containsValueForKey:NetworkMacKey]) { + _mac = (NSString*)[aDecoder decodeObjectForKey:NetworkMacKey]; + } + + if([aDecoder containsValueForKey:NetworkMtuKey]) { + _mtu = (int)[aDecoder decodeIntegerForKey:NetworkMtuKey]; + } + + if([aDecoder containsValueForKey:NetworkNameKey]) { + _name = (NSString*)[aDecoder decodeObjectForKey:NetworkNameKey]; + } + + if([aDecoder containsValueForKey:NetworkNetconfKey]) { + _netconfRevision = (int)[aDecoder decodeIntegerForKey:NetworkNetconfKey]; + } + + if([aDecoder containsValueForKey:NetworkNwidKey]) { + _nwid = [(NSNumber*)[aDecoder decodeObjectForKey:NetworkNwidKey] unsignedLongLongValue]; + } + + if([aDecoder containsValueForKey:NetworkPortNameKey]) { + _portDeviceName = (NSString*)[aDecoder decodeObjectForKey:NetworkPortNameKey]; + } + + if([aDecoder containsValueForKey:NetworkPortErrorKey]) { + _portError = (int)[aDecoder decodeIntegerForKey:NetworkPortErrorKey]; + } + + if([aDecoder containsValueForKey:NetworkStatusKey]) { + _status = (enum NetworkStatus)[aDecoder decodeIntegerForKey:NetworkStatusKey]; + } + + if([aDecoder containsValueForKey:NetworkTypeKey]) { + _type = (enum NetworkType)[aDecoder decodeIntegerForKey:NetworkTypeKey]; + } + + if([aDecoder containsValueForKey:NetworkAllowManagedKey]) { + _allowManaged = [aDecoder decodeBoolForKey:NetworkAllowManagedKey]; + } + + if([aDecoder containsValueForKey:NetworkAllowGlobalKey]) { + _allowGlobal = [aDecoder decodeBoolForKey:NetworkAllowGlobalKey]; + } + + if([aDecoder containsValueForKey:NetworkAllowDefaultKey]) { + _allowDefault = [aDecoder decodeBoolForKey:NetworkAllowDefaultKey]; + } + + _connected = NO; + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder +{ + [aCoder encodeObject:_assignedAddresses forKey:NetworkAddressesKey]; + [aCoder encodeBool:_bridge forKey:NetworkBridgeKey]; + [aCoder encodeBool:_broadcastEnabled forKey:NetworkBroadcastKey]; + [aCoder encodeBool:_dhcp forKey:NetworkDhcpKey]; + [aCoder encodeObject:_mac forKey:NetworkMacKey]; + [aCoder encodeInteger:_mtu forKey:NetworkMtuKey]; + [aCoder encodeObject:_name forKey:NetworkNameKey]; + [aCoder encodeInteger:_netconfRevision forKey:NetworkNetconfKey]; + [aCoder encodeObject:[NSNumber numberWithUnsignedLongLong:_nwid] + forKey:NetworkNwidKey]; + [aCoder encodeObject:_portDeviceName forKey:NetworkPortNameKey]; + [aCoder encodeInteger:_portError forKey:NetworkPortErrorKey]; + [aCoder encodeInteger:_status forKey:NetworkStatusKey]; + [aCoder encodeInteger:_type forKey:NetworkTypeKey]; + [aCoder encodeBool:_allowManaged forKey:NetworkAllowManagedKey]; + [aCoder encodeBool:_allowGlobal forKey:NetworkAllowGlobalKey]; + [aCoder encodeBool:_allowDefault forKey:NetworkAllowDefaultKey]; +} + ++ (BOOL)defaultRouteExists:(NSArray*)netList +{ + for(Network *net in netList) { + if (net.allowDefault && net.connected) { + return YES; + } + } + return NO; +} + +- (NSString*)statusString { + switch(_status) { + case REQUESTING_CONFIGURATION: + return @"REQUESTING_CONFIGURATION"; + case OK: + return @"OK"; + case ACCESS_DENIED: + return @"ACCESS_DENIED"; + case NOT_FOUND: + return @"NOT_FOUND"; + case PORT_ERROR: + return @"PORT_ERROR"; + case CLIENT_TOO_OLD: + return @"CLIENT_TOO_OLD"; + default: + return @""; + } +} + +- (NSString*)typeString { + switch(_type) { + case PUBLIC: + return @"PUBLIC"; + case PRIVATE: + return @"PRIVATE"; + default: + return @""; + } +} + +- (BOOL)hasSameNetworkId:(UInt64)networkId +{ + return self.nwid == networkId; +} + +- (BOOL)isEqualToNetwork:(Network*)network +{ + return [self.assignedAddresses isEqualToArray:network.assignedAddresses] && + self.bridge == network.bridge && + self.broadcastEnabled == network.broadcastEnabled && + self.dhcp == network.dhcp && + [self.mac isEqualToString:network.mac] && + self.mtu == network.mtu && + self.netconfRevision == network.netconfRevision && + [self.name isEqualToString:network.name] && + self.nwid == network.nwid && + [self.portDeviceName isEqualToString:network.portDeviceName] && + self.status == network.status && + self.type == network.type && + self.allowManaged == network.allowManaged && + self.allowGlobal == network.allowGlobal && + self.allowDefault == network.allowDefault && + self.connected == network.connected; +} + +- (BOOL)isEqual:(id)object +{ + if (self == object) { + return YES; + } + + if (![object isKindOfClass:[Network class]]) { + return NO; + } + + return [self isEqualToNetwork:object]; +} + +- (NSUInteger)hash +{ + return [self.assignedAddresses hash] ^ + self.bridge ^ + self.broadcastEnabled ^ + self.dhcp ^ + [self.mac hash] ^ + self.mtu ^ + self.netconfRevision ^ + [self.name hash] ^ + self.nwid ^ + [self.portDeviceName hash] ^ + self.portError ^ + self.status ^ + self.type ^ + self.allowManaged ^ + self.allowGlobal ^ + self.allowDefault ^ + self.connected; +} + +@end diff --git a/macui/ZeroTier One/NetworkInfoCell.h b/macui/ZeroTier One/NetworkInfoCell.h new file mode 100644 index 0000000..be9345d --- /dev/null +++ b/macui/ZeroTier One/NetworkInfoCell.h @@ -0,0 +1,50 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import + +@class ShowNetworksViewController; + +@interface NetworkInfoCell : NSTableCellView + +@property (weak, nonatomic) ShowNetworksViewController *parent; + +@property (weak, nonatomic) IBOutlet NSTextField *networkIdField; +@property (weak, nonatomic) IBOutlet NSTextField *networkNameField; +@property (weak, nonatomic) IBOutlet NSTextField *statusField; +@property (weak, nonatomic) IBOutlet NSTextField *typeField; +@property (weak, nonatomic) IBOutlet NSTextField *macField; +@property (weak, nonatomic) IBOutlet NSTextField *mtuField; +@property (weak, nonatomic) IBOutlet NSTextField *broadcastField; +@property (weak, nonatomic) IBOutlet NSTextField *bridgingField; +@property (weak, nonatomic) IBOutlet NSTextField *deviceField; +@property (weak, nonatomic) IBOutlet NSTextField *addressesField; +@property (weak, nonatomic) IBOutlet NSButton *allowManaged; +@property (weak, nonatomic) IBOutlet NSButton *allowGlobal; +@property (weak, nonatomic) IBOutlet NSButton *allowDefault; +@property (weak, nonatomic) IBOutlet NSButton *connectedCheckbox; +@property (weak, nonatomic) IBOutlet NSButton *deleteButton; + +- (IBAction)onConnectCheckStateChanged:(NSButton*)sender; +- (IBAction)deleteNetwork:(NSButton*)sender; +- (IBAction)onAllowStatusChanged:(NSButton*)sender; + +- (void)joinNetwork:(NSString*)nwid; +- (void)leaveNetwork:(NSString*)nwid; + +@end diff --git a/macui/ZeroTier One/NetworkInfoCell.m b/macui/ZeroTier One/NetworkInfoCell.m new file mode 100644 index 0000000..dc75cab --- /dev/null +++ b/macui/ZeroTier One/NetworkInfoCell.m @@ -0,0 +1,85 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import "NetworkInfoCell.h" +#import "ServiceCom.h" +#import "ShowNetworksViewController.h" +#import "Network.h" + +@implementation NetworkInfoCell + +- (void)drawRect:(NSRect)dirtyRect { + [super drawRect:dirtyRect]; + + // Drawing code here. +} + +- (IBAction)onConnectCheckStateChanged:(NSButton*)sender +{ + if(sender.state == NSOnState) { + [self joinNetwork:self.networkIdField.stringValue]; + } + else { + [self leaveNetwork:self.networkIdField.stringValue]; + } +} + +- (IBAction)deleteNetwork:(NSButton*)sender; +{ + [self leaveNetwork:self.networkIdField.stringValue]; + [self.parent deleteNetworkFromList:self.networkIdField.stringValue]; +} + +- (IBAction)onAllowStatusChanged:(NSButton*)sender +{ + [self joinNetwork:self.networkIdField.stringValue]; +} + +- (void)joinNetwork:(NSString*)nwid +{ + NSError *error = nil; + [[ServiceCom sharedInstance] joinNetwork:nwid + allowManaged:(self.allowManaged.state == NSOnState) + allowGlobal:(self.allowGlobal.state == NSOnState) + allowDefault:![Network defaultRouteExists:_parent.networkList] && (self.allowDefault.state == NSOnState) + error:&error]; + + if (error) { + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Ok"]; + + [alert runModal]; + } +} + +- (void)leaveNetwork:(NSString*)nwid +{ + NSError *error = nil; + [[ServiceCom sharedInstance] leaveNetwork:nwid error:&error]; + + if (error) { + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Ok"]; + + [alert runModal]; + } +} + +@end diff --git a/macui/ZeroTier One/NetworkMonitor.h b/macui/ZeroTier One/NetworkMonitor.h new file mode 100644 index 0000000..8cdec4e --- /dev/null +++ b/macui/ZeroTier One/NetworkMonitor.h @@ -0,0 +1,45 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import + +extern NSString * const NetworkUpdateKey; +extern NSString * const StatusUpdateKey; + +@class Network; + +@interface NetworkMonitor : NSObject +{ + NSMutableArray *_savedNetworks; + NSArray *_receivedNetworks; + NSMutableArray *_allNetworks; + + NSTimer *_timer; +} + +- (id)init; +- (void)dealloc; + +- (void)start; +- (void)stop; + +- (void)updateNetworkInfo; + +- (void)deleteSavedNetwork:(NSString*)networkId; + +@end diff --git a/macui/ZeroTier One/NetworkMonitor.m b/macui/ZeroTier One/NetworkMonitor.m new file mode 100644 index 0000000..7ed22c4 --- /dev/null +++ b/macui/ZeroTier One/NetworkMonitor.m @@ -0,0 +1,253 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import "NetworkMonitor.h" +#import "Network.h" +#import "ServiceCom.h" +#import "NodeStatus.h" + +@import AppKit; + + +NSString * const NetworkUpdateKey = @"com.zerotier.one.network-list"; +NSString * const StatusUpdateKey = @"com.zerotier.one.status"; + +@interface NetworkMonitor (private) + +- (NSString*)dataFile; +- (void)internal_updateNetworkInfo; +- (NSInteger)findNetworkWithID:(UInt64)networkId; +- (NSInteger)findSavedNetworkWithID:(UInt64)networkId; +- (void)saveNetworks; + +@end + +@implementation NetworkMonitor + +- (id)init +{ + self = [super init]; + if(self) + { + _savedNetworks = [NSMutableArray array]; + _receivedNetworks = [NSArray array]; + _allNetworks = [NSMutableArray array]; + _timer = nil; + } + + return self; +} + +- (void)dealloc +{ + [_timer invalidate]; +} + +- (void)start +{ + NSLog(@"ZeroTier monitor started"); + _timer = [NSTimer scheduledTimerWithTimeInterval:1.0f + target:self + selector:@selector(updateNetworkInfo) + userInfo:nil + repeats:YES]; +} + +- (void)stop +{ + NSLog(@"ZeroTier monitor stopped"); + [_timer invalidate]; + _timer = nil; +} + +- (void)updateNetworkInfo +{ + NSString *filePath = [self dataFile]; + + if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + NSArray *networks = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + + if(networks != nil) { + _savedNetworks = [networks mutableCopy]; + } + } + + NSError *error = nil; + + [[ServiceCom sharedInstance] getNetworklist:^(NSArray *networkList) { + _receivedNetworks = networkList; + + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [self internal_updateNetworkInfo]; + } ]; + } error:&error]; + + if(error) { + [self stop]; + + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res = [alert runModal]; + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + } + else if(res == NSAlertSecondButtonReturn) { + [self start]; + return; + } + } + + [[ServiceCom sharedInstance] getNodeStatus:^(NodeStatus *status) { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:status forKey:@"status"]; + + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [[NSNotificationCenter defaultCenter] postNotificationName:StatusUpdateKey + object:nil + userInfo:userInfo]; + }]; + } error:&error]; + + if (error) { + [self stop]; + + NSAlert *alert = [NSAlert alertWithError:error]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res = [alert runModal]; + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + } + else if(res == NSAlertSecondButtonReturn) { + [self start]; + return; + } + } +} + +- (void)deleteSavedNetwork:(NSString*)networkId +{ + UInt64 nwid = 0; + NSScanner *scanner = [NSScanner scannerWithString:networkId]; + [scanner scanHexLongLong:&nwid]; + + NSInteger index = [self findNetworkWithID:nwid]; + + if(index != NSNotFound) { + [_allNetworks removeObjectAtIndex:index]; + } + + index = [self findSavedNetworkWithID:nwid]; + + if(index != NSNotFound) { + [_savedNetworks removeObjectAtIndex:index]; + } + + [self saveNetworks]; +} + +@end + +@implementation NetworkMonitor (private) +- (NSString*)dataFile +{ + NSURL *appSupport = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory + inDomains:NSUserDomainMask] objectAtIndex:0]; + + appSupport = [[[appSupport URLByAppendingPathComponent:@"ZeroTier"] URLByAppendingPathComponent:@"One"] URLByAppendingPathComponent:@"networkinfo.dat"]; + return appSupport.path; +} + +- (void)internal_updateNetworkInfo +{ + NSMutableArray *networks = [_savedNetworks mutableCopy]; + + for(Network *nw in _receivedNetworks) { + NSInteger index = [self findSavedNetworkWithID:nw.nwid]; + + if(index != NSNotFound) { + [networks setObject:nw atIndexedSubscript:index]; + } + else { + [networks addObject:nw]; + } + } + + [networks sortUsingComparator:^NSComparisonResult(Network *obj1, Network *obj2) { + if(obj1.nwid > obj2.nwid) { + return true; + } + return false; + }]; + + @synchronized(_allNetworks) { + _allNetworks = networks; + } + + [self saveNetworks]; + + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:networks forKey:@"networks"]; + + [[NSNotificationCenter defaultCenter] postNotificationName:NetworkUpdateKey + object:nil + userInfo:userInfo]; +} + +- (NSInteger)findNetworkWithID:(UInt64)networkId +{ + for(int i = 0; i < [_allNetworks count]; ++i) { + Network *nw = [_allNetworks objectAtIndex:i]; + + if(nw.nwid == networkId) { + return i; + } + } + + return NSNotFound; +} + + +- (NSInteger)findSavedNetworkWithID:(UInt64)networkId +{ + for(int i = 0; i < [_savedNetworks count]; ++i) { + Network *nw = [_savedNetworks objectAtIndex:i]; + + if(nw.nwid == networkId) { + return i; + } + } + + return NSNotFound; +} + +- (void)saveNetworks +{ + NSString *filePath = [self dataFile]; + + @synchronized(_allNetworks) { + [NSKeyedArchiver archiveRootObject:_allNetworks toFile:filePath]; + } +} + +@end diff --git a/macui/ZeroTier One/NodeStatus.h b/macui/ZeroTier One/NodeStatus.h new file mode 100644 index 0000000..eab5bfe --- /dev/null +++ b/macui/ZeroTier One/NodeStatus.h @@ -0,0 +1,35 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import + +@interface NodeStatus : NSObject + +@property (readonly) NSString *address; +@property (readonly) NSString *publicIdentity; +@property (readonly) BOOL online; +@property (readonly) BOOL tcpFallbackActive; +@property (readonly) int versionMajor; +@property (readonly) int versionMinor; +@property (readonly) int versionRev; +@property (readonly) NSString *version; +@property (readonly) UInt64 clock; + +- (id)initWithJsonData:(NSDictionary*)jsonData; + +@end diff --git a/macui/ZeroTier One/NodeStatus.m b/macui/ZeroTier One/NodeStatus.m new file mode 100644 index 0000000..3bae3c7 --- /dev/null +++ b/macui/ZeroTier One/NodeStatus.m @@ -0,0 +1,40 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ +#import "NodeStatus.h" + +@implementation NodeStatus + +- (id)initWithJsonData:(NSDictionary*)jsonData +{ + self = [super init]; + + if(self) { + _address = (NSString*)[jsonData objectForKey:@"address"]; + _publicIdentity = (NSString*)[jsonData objectForKey:@"publicIdentity"]; + _online = [(NSNumber*)[jsonData objectForKey:@"online"] boolValue]; + _tcpFallbackActive = [(NSNumber*)[jsonData objectForKey:@"tcpFallbackActive"] boolValue]; + _versionMajor = [(NSNumber*)[jsonData objectForKey:@"versionMajor"] intValue]; + _versionMinor = [(NSNumber*)[jsonData objectForKey:@"versionMinor"] intValue]; + _versionRev = [(NSNumber*)[jsonData objectForKey:@"versionRev"] intValue]; + _version = (NSString*)[jsonData objectForKey:@"version"]; + _clock = [(NSNumber*)[jsonData objectForKey:@"clock"] unsignedLongLongValue]; + } + + return self; +} +@end diff --git a/macui/ZeroTier One/PreferencesViewController.h b/macui/ZeroTier One/PreferencesViewController.h new file mode 100644 index 0000000..56d0fdb --- /dev/null +++ b/macui/ZeroTier One/PreferencesViewController.h @@ -0,0 +1,31 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import + +@interface PreferencesViewController : NSViewController + +@property (nonatomic, weak) IBOutlet NSButton *startupCheckBox; + +- (IBAction)onStartupCheckBoxChanged:(NSButton*)sender; + +- (BOOL)isLaunchAtStartup; +- (LSSharedFileListItemRef)itemRefInLoginItems; +- (void)setLaunchAtLoginEnabled:(BOOL)enabled; + +@end diff --git a/macui/ZeroTier One/PreferencesViewController.m b/macui/ZeroTier One/PreferencesViewController.m new file mode 100644 index 0000000..13927fb --- /dev/null +++ b/macui/ZeroTier One/PreferencesViewController.m @@ -0,0 +1,112 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import "PreferencesViewController.h" + +@interface PreferencesViewController () + +@end + +@implementation PreferencesViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + if([self isLaunchAtStartup]) { + self.startupCheckBox.state = NSOnState; + } + else { + self.startupCheckBox.state = NSOffState; + } +} + +- (IBAction)onStartupCheckBoxChanged:(NSButton *)sender +{ + if(sender.state == NSOnState) { + [self setLaunchAtLoginEnabled:YES]; + } + else { + [self setLaunchAtLoginEnabled:NO]; + } + +} + +- (void)setLaunchAtLoginEnabled:(BOOL)enabled +{ + LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); + + if (enabled) { + // Add the app to the LoginItems list. + CFURLRef appUrl = (__bridge CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; + LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, appUrl, NULL, NULL); + if (itemRef) CFRelease(itemRef); + } + else { + // Remove the app from the LoginItems list. + LSSharedFileListItemRef itemRef = [self itemRefInLoginItems]; + LSSharedFileListItemRemove(loginItemsRef,itemRef); + if (itemRef != nil) CFRelease(itemRef); + } +} + + +- (BOOL)isLaunchAtStartup { + // See if the app is currently in LoginItems. + LSSharedFileListItemRef itemRef = [self itemRefInLoginItems]; + // Store away that boolean. + BOOL isInList = itemRef != nil; + // Release the reference if it exists. + if (itemRef != nil) CFRelease(itemRef); + + return isInList; +} + +- (LSSharedFileListItemRef)itemRefInLoginItems { + LSSharedFileListItemRef itemRef = nil; + + NSString * appPath = [[NSBundle mainBundle] bundlePath]; + + // This will retrieve the path for the application + // For example, /Applications/test.app + CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:appPath]; + + // Create a reference to the shared file list. + LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); + + if (loginItems) { + UInt32 seedValue; + //Retrieve the list of Login Items and cast them to + // a NSArray so that it will be easier to iterate. + NSArray *loginItemsArray = (__bridge NSArray *)LSSharedFileListCopySnapshot(loginItems, &seedValue); + for(int i = 0; i< [loginItemsArray count]; i++){ + LSSharedFileListItemRef currentItemRef = (__bridge LSSharedFileListItemRef)[loginItemsArray + objectAtIndex:i]; + //Resolve the item with URL + if (LSSharedFileListItemResolve(currentItemRef, 0, (CFURLRef*) &url, NULL) == noErr) { + NSString * urlPath = [(__bridge NSURL*)url path]; + if ([urlPath compare:appPath] == NSOrderedSame){ + itemRef = currentItemRef; + } + } + } + } + CFRelease(loginItems); + return itemRef; +} + +@end diff --git a/macui/ZeroTier One/PreferencesViewController.xib b/macui/ZeroTier One/PreferencesViewController.xib new file mode 100644 index 0000000..62aef4c --- /dev/null +++ b/macui/ZeroTier One/PreferencesViewController.xib @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macui/ZeroTier One/ServiceCom.h b/macui/ZeroTier One/ServiceCom.h new file mode 100644 index 0000000..c2b4692 --- /dev/null +++ b/macui/ZeroTier One/ServiceCom.h @@ -0,0 +1,40 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import + +@class NodeStatus; +@class Network; + +@interface ServiceCom : NSObject +{ + NSString *baseURL; + NSURLSession *session; + BOOL _isQuitting; + BOOL _resetKey; +} ++ (ServiceCom*)sharedInstance; + +- (id)init; + +- (void)getNetworklist:(void (^)(NSArray*))completionHandler error:(NSError* __autoreleasing *)error; +- (void)getNodeStatus:(void (^)(NodeStatus*))completionHandler error:(NSError*__autoreleasing*)error; +- (void)joinNetwork:(NSString*)networkId allowManaged:(BOOL)allowManaged allowGlobal:(BOOL)allowGlobal allowDefault:(BOOL)allowDefault error:(NSError*__autoreleasing*)error; +- (void)leaveNetwork:(NSString*)networkId error:(NSError*__autoreleasing*)error; + +@end diff --git a/macui/ZeroTier One/ServiceCom.m b/macui/ZeroTier One/ServiceCom.m new file mode 100644 index 0000000..75b9850 --- /dev/null +++ b/macui/ZeroTier One/ServiceCom.m @@ -0,0 +1,516 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import "ServiceCom.h" +#import "AuthtokenCopy.h" +#import "Network.h" +#import "NodeStatus.h" +@import AppKit; + +@interface ServiceCom (Private) + +- (NSString*)key; + +@end + +@implementation ServiceCom + ++ (ServiceCom*)sharedInstance { + static ServiceCom *sc = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sc = [[ServiceCom alloc] init]; + }); + return sc; +} + +- (id)init +{ + self = [super init]; + if(self) { + baseURL = @"http://127.0.0.1:9993"; + session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; + _isQuitting = NO; + _resetKey = NO; + } + + return self; +} + +- (NSString*)key:(NSError* __autoreleasing *)err +{ + static NSString *k = nil; + static NSUInteger resetCount = 0; + + @synchronized (self) { + if (_isQuitting) { + return @""; + } + + if (_resetKey && k != nil) { + k = nil; + ++resetCount; + NSLog(@"ResetCount: %lu", (unsigned long)resetCount); + if (resetCount > 10) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithMessageText:@"Error obtaining Auth Token" + defaultButton:@"Quit" + alternateButton:@"Retry" + otherButton:nil + informativeTextWithFormat:@"Please ensure ZeroTier is installed correctly"]; + alert.alertStyle = NSCriticalAlertStyle; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == 1) { + _isQuitting = YES; + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + } + }]; + return @""; + } + } + + if (k == nil) { + NSError *error = nil; + NSURL *appSupportDir = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:false error:&error]; + + if (error) { + NSLog(@"Error: %@", error); + return @""; + } + + appSupportDir = [[appSupportDir URLByAppendingPathComponent:@"ZeroTier"] URLByAppendingPathComponent:@"One"]; + NSURL *authtokenURL = [appSupportDir URLByAppendingPathComponent:@"authtoken.secret"]; + + if (!_resetKey && [[NSFileManager defaultManager] fileExistsAtPath:[authtokenURL path]]) { + k = [NSString stringWithContentsOfURL:authtokenURL + encoding:NSUTF8StringEncoding + error:&error]; + + k = [k stringByReplacingOccurrencesOfString:@"\n" withString:@""]; + + if (error) { + NSLog(@"Error: %@", error); + k = nil; + *err = error; + return @""; + } + } + else { + _resetKey = NO; + NSURL *sysAppSupportDir = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSSystemDomainMask appropriateForURL:nil create:false error:nil]; + + sysAppSupportDir = [[sysAppSupportDir URLByAppendingPathComponent:@"ZeroTier"] URLByAppendingPathComponent:@"One"]; + NSURL *sysAuthtokenURL = [sysAppSupportDir URLByAppendingPathComponent:@"authtoken.secret"]; + + if(![[NSFileManager defaultManager] fileExistsAtPath:[sysAuthtokenURL path]]) { + + } + + [[NSFileManager defaultManager] createDirectoryAtURL:appSupportDir + withIntermediateDirectories:YES + attributes:nil + error:&error]; + + if (error) { + NSLog(@"Error: %@", error); + *err = error; + k = nil; + return @""; + } + + AuthorizationRef authRef; + OSStatus status = AuthorizationCreate(nil, nil, kAuthorizationFlagDefaults, &authRef); + + if (status != errAuthorizationSuccess) { + NSLog(@"Authorization Failed! %d", status); + + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Couldn't create AuthorizationRef", nil), + }; + *err = [NSError errorWithDomain:@"com.zerotier.one" code:-1 userInfo:userInfo]; + + return @""; + } + + AuthorizationItem authItem; + authItem.name = kAuthorizationRightExecute; + authItem.valueLength = 0; + authItem.flags = 0; + + AuthorizationRights authRights; + authRights.count = 1; + authRights.items = &authItem; + + AuthorizationFlags authFlags = kAuthorizationFlagDefaults | + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagPreAuthorize | + kAuthorizationFlagExtendRights; + + status = AuthorizationCopyRights(authRef, &authRights, nil, authFlags, nil); + + if (status != errAuthorizationSuccess) { + NSLog(@"Authorization Failed! %d", status); + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Couldn't copy authorization rights", nil), + }; + *err = [NSError errorWithDomain:@"com.zerotier.one" code:-1 userInfo:userInfo]; + return @""; + } + + NSString *localKey = getAdminAuthToken(authRef); + AuthorizationFree(authRef, kAuthorizationFlagDestroyRights); + + if (localKey != nil && [localKey lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > 0) { + k = localKey; + + [localKey writeToURL:authtokenURL + atomically:YES + encoding:NSUTF8StringEncoding + error:&error]; + + if (error) { + NSLog(@"Error writing token to disk: %@", error); + *err = error; + } + } + } + } + + if (k == nil) { + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Unknown error finding authorization key", nil), + }; + *err = [NSError errorWithDomain:@"com.zerotier.one" code:-1 userInfo:userInfo]; + + return @""; + } + } + return k; +} + +- (void)getNetworklist:(void (^)(NSArray *))completionHandler error:(NSError *__autoreleasing*)error +{ + NSString* key = [self key:error]; + if(*error) { + return; + } + + NSString *urlString = [[baseURL stringByAppendingString:@"/network?auth="] stringByAppendingString:key]; + + NSURL *url = [NSURL URLWithString:urlString]; + NSURLSessionDataTask *task = + [session dataTaskWithURL:url + completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) { + + if (err) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithError:err]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + _isQuitting = YES; + } + }]; + return; + } + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; + NSInteger status = [httpResponse statusCode]; + + NSError *err2; + + if (status == 200) { + NSArray *json = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&err2]; + if (err) { + NSLog(@"Error fetching network list: %@", err2); + + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithError:err2]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == NSAlertFirstButtonReturn) { + _isQuitting = YES; + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + } + }]; + return; + } + + NSMutableArray *networks = [[NSMutableArray alloc] init]; + for(NSDictionary *dict in json) { + [networks addObject:[[Network alloc] initWithJsonData:dict]]; + } + + completionHandler(networks); + } + else if (status == 401) { + self->_resetKey = YES; + } + }]; + [task resume]; +} + +- (void)getNodeStatus:(void (^)(NodeStatus*))completionHandler error:(NSError*__autoreleasing*)error +{ + NSString *key = [self key:error]; + if(*error) { + return; + } + + NSString *urlString = [[baseURL stringByAppendingString:@"/status?auth="] stringByAppendingString:key]; + + NSURL *url = [NSURL URLWithString:urlString]; + NSURLSessionDataTask *task = + [session dataTaskWithURL:url + completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) { + + if(err) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithError:err]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + _isQuitting = YES; + } + }]; + return; + } + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; + NSInteger status = [httpResponse statusCode]; + + NSError *err2; + if(status == 200) { + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&err2]; + + if(err2) { + NSLog(@"Error fetching node status: %@", err2); + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithError:err2]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + _isQuitting = YES; + } + }]; + return; + } + + NodeStatus *status = [[NodeStatus alloc] initWithJsonData:json]; + + completionHandler(status); + } + else if (status == 401) { + self->_resetKey = YES; + } + }]; + [task resume]; +} + +- (void)joinNetwork:(NSString*)networkId allowManaged:(BOOL)allowManaged allowGlobal:(BOOL)allowGlobal allowDefault:(BOOL)allowDefault error:(NSError *__autoreleasing*)error +{ + NSString *key = [self key:error]; + if(*error) { + return; + } + + NSString *urlString = [[[[baseURL stringByAppendingString:@"/network/"] + stringByAppendingString:networkId] + stringByAppendingString:@"?auth="] + stringByAppendingString:key]; + + NSURL *url = [NSURL URLWithString:urlString]; + + NSMutableDictionary *jsonDict = [NSMutableDictionary dictionary]; + [jsonDict setObject:[NSNumber numberWithBool:allowManaged] forKey:@"allowManaged"]; + [jsonDict setObject:[NSNumber numberWithBool:allowGlobal] forKey:@"allowGlobal"]; + [jsonDict setObject:[NSNumber numberWithBool:allowDefault] forKey:@"allowDefault"]; + + NSError *err = nil; + + NSData *json = [NSJSONSerialization dataWithJSONObject:jsonDict + options:0 + error:&err]; + + if(err) { + NSLog(@"Error creating json data: %@", err); + *error = err; + return; + } + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + request.HTTPMethod = @"POST"; + request.HTTPBody = json; + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + + NSURLSessionDataTask *task = + [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) { + if(err) { + NSLog(@"Error posting join request: %@", err); + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithError:err]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + _isQuitting = YES; + } + }]; + } + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; + NSInteger status = [httpResponse statusCode]; + + if(status == 200) { + NSLog(@"join ok"); + } + else if (status == 401) { + self->_resetKey = YES; + } + else { + NSLog(@"join error: %ld", (long)status); + } + }]; + [task resume]; +} + +- (void)leaveNetwork:(NSString*)networkId error:(NSError*__autoreleasing*)error +{ + NSString *key = [self key:error]; + if(*error) { + return; + } + + NSString *urlString = [[[[baseURL stringByAppendingString:@"/network/"] + stringByAppendingString:networkId] + stringByAppendingString:@"?auth="] + stringByAppendingString:key]; + + NSURL *url = [NSURL URLWithString:urlString]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + request.HTTPMethod = @"DELETE"; + + NSURLSessionDataTask *task = + [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable err) { + if(err) { + NSLog(@"Error posting delete request: %@", err); + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + NSAlert *alert = [NSAlert alertWithError:err]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert addButtonWithTitle:@"Retry"]; + + NSModalResponse res; + if (!_isQuitting) { + res = [alert runModal]; + } + else { + return; + } + + if(res == NSAlertFirstButtonReturn) { + [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; + _isQuitting = YES; + } + }]; + return; + } + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; + NSInteger status = httpResponse.statusCode; + + if(status == 200) { + NSLog(@"leave ok"); + } + else if (status == 401) { + self->_resetKey = YES; + } + else { + NSLog(@"leave error: %ld", status); + } + }]; + [task resume]; +} + +@end diff --git a/macui/ZeroTier One/ShowNetworksViewController.h b/macui/ZeroTier One/ShowNetworksViewController.h new file mode 100644 index 0000000..6138958 --- /dev/null +++ b/macui/ZeroTier One/ShowNetworksViewController.h @@ -0,0 +1,36 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import + +@class NetworkMonitor; +@class Network; + +@interface ShowNetworksViewController : NSViewController + +@property (nonatomic) NSMutableArray *networkList; +@property (nonatomic) NetworkMonitor *netMonitor; +@property (nonatomic) BOOL visible; + +@property (weak, nonatomic) IBOutlet NSTableView *tableView; + +- (void)deleteNetworkFromList:(NSString*)nwid; +- (void)setNetworks:(NSArray*)list; + + +@end diff --git a/macui/ZeroTier One/ShowNetworksViewController.m b/macui/ZeroTier One/ShowNetworksViewController.m new file mode 100644 index 0000000..903a4b4 --- /dev/null +++ b/macui/ZeroTier One/ShowNetworksViewController.m @@ -0,0 +1,181 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import "ShowNetworksViewController.h" +#import "NetworkMonitor.h" +#import "NetworkInfoCell.h" +#import "Network.h" + +BOOL hasNetworkWithID(NSArray *list, UInt64 nwid) +{ + for(Network *n in list) { + if(n.nwid == nwid) { + return YES; + } + } + + return NO; +} + +@interface ShowNetworksViewController () + +@end + +@implementation ShowNetworksViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.networkList = [NSMutableArray array]; + + [self.tableView setDelegate:self]; + [self.tableView setDataSource:self]; + [self.tableView setBackgroundColor:[NSColor clearColor]]; +} + +- (void)viewWillAppear { + [super viewWillAppear]; + self.visible = YES; +} + +- (void)viewWillDisappear { + [super viewWillDisappear]; + self.visible = NO; +} + +- (NSInteger)findNetworkWithID:(UInt64)networkId +{ + for(int i = 0; i < [_networkList count]; ++i) { + Network *nw = [_networkList objectAtIndex:i]; + + if(nw.nwid == networkId) { + return i; + } + } + + return NSNotFound; +} + + +- (void)deleteNetworkFromList:(NSString *)nwid { + [self.netMonitor deleteSavedNetwork:nwid]; + + UInt64 netid = 0; + NSScanner *scanner = [NSScanner scannerWithString:nwid]; + [scanner scanHexLongLong:&netid]; + for (Network *n in _networkList) { + if (n.nwid == netid) { + NSInteger index = [self findNetworkWithID:netid]; + + if (index != NSNotFound) { + [_networkList removeObjectAtIndex:index]; + [_tableView reloadData]; + } + } + } +} + +- (void)setNetworks:(NSArray *)list { + for (Network *n in list) { + if ([_networkList containsObject:n]) { + // don't need to do anything here. Already an identical object in the list + continue; + } + else { + // network not in the list based on equality. Did an object change? or is it a new item? + if (hasNetworkWithID(_networkList, n.nwid)) { + + for (int i = 0; i < [_networkList count]; ++i) { + Network *n2 = [_networkList objectAtIndex:i]; + if (n.nwid == n2.nwid) + { + [_networkList replaceObjectAtIndex:i withObject:n]; + [_tableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:i] + columnIndexes:[NSIndexSet indexSetWithIndex:0]]; + } + } + } + else { + [_networkList addObject:n]; + [_tableView reloadData]; + } + } + } +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { + return [_networkList count]; +} + +- (NSView*)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +{ + NetworkInfoCell *cell = (NetworkInfoCell*)[tableView makeViewWithIdentifier:@"NetworkInfoCell" + owner:nil]; + Network *network = [_networkList objectAtIndex:row]; + cell.parent = self; + cell.networkIdField.stringValue = [NSString stringWithFormat:@"%10llx", network.nwid]; + cell.networkNameField.stringValue = network.name; + cell.statusField.stringValue = [network statusString]; + cell.typeField.stringValue = [network typeString]; + cell.mtuField.stringValue = [NSString stringWithFormat:@"%d", network.mtu]; + cell.macField.stringValue = network.mac; + cell.broadcastField.stringValue = network.broadcastEnabled ? @"ENABLED" : @"DISABLED"; + cell.bridgingField.stringValue = network.bridge ? @"ENABLED" : @"DISABLED"; + cell.deviceField.stringValue = network.portDeviceName; + + if(network.connected) { + cell.connectedCheckbox.state = NSOnState; + + if(network.allowDefault) { + cell.allowDefault.enabled = YES; + cell.allowDefault.state = NSOnState; + } + else { + cell.allowDefault.state = NSOffState; + + if([Network defaultRouteExists:_networkList]) { + cell.allowDefault.enabled = NO; + } + else { + cell.allowDefault.enabled = YES; + } + } + + cell.allowGlobal.enabled = YES; + cell.allowManaged.enabled = YES; + } + else { + cell.connectedCheckbox.state = NSOffState; + cell.allowDefault.enabled = NO; + cell.allowGlobal.enabled = NO; + cell.allowManaged.enabled = NO; + } + + cell.allowGlobal.state = network.allowGlobal ? NSOnState : NSOffState; + cell.allowManaged.state = network.allowManaged ? NSOnState : NSOffState; + + cell.addressesField.stringValue = @""; + + for(NSString *addr in network.assignedAddresses) { + cell.addressesField.stringValue = [[cell.addressesField.stringValue stringByAppendingString:addr] stringByAppendingString:@"\n"]; + } + + return cell; +} + +@end diff --git a/macui/ZeroTier One/ShowNetworksViewController.xib b/macui/ZeroTier One/ShowNetworksViewController.xib new file mode 100644 index 0000000..62ac289 --- /dev/null +++ b/macui/ZeroTier One/ShowNetworksViewController.xib @@ -0,0 +1,371 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macui/ZeroTier One/ZeroTierIcon.icns b/macui/ZeroTier One/ZeroTierIcon.icns new file mode 100644 index 0000000..17e60d5 Binary files /dev/null and b/macui/ZeroTier One/ZeroTierIcon.icns differ diff --git a/macui/ZeroTier One/about.html b/macui/ZeroTier One/about.html new file mode 100644 index 0000000..4fa41d7 --- /dev/null +++ b/macui/ZeroTier One/about.html @@ -0,0 +1,65 @@ + + + + + + +
+
+
+
+
+ +
+

Getting Started

+ +

Getting started is simple. Simply click Join Network from the ZeroTier status bar menu. To join the public network "Earth", enter 8056c2e21c000001 and click the Join button. Once connected, you'll be able to navigate to earth.zerotier.net.

+ +

Create a Network

+

Visit my.zerotier.com to create and manage your own virtual networks.

+ +

For more information, visit zerotier.com.

+ +
+ + \ No newline at end of file diff --git a/macui/ZeroTier One/main.m b/macui/ZeroTier One/main.m new file mode 100644 index 0000000..108a6bd --- /dev/null +++ b/macui/ZeroTier One/main.m @@ -0,0 +1,23 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#import + +int main(int argc, const char * argv[]) { + return NSApplicationMain(argc, argv); +} diff --git a/make-bsd.mk b/make-bsd.mk new file mode 100644 index 0000000..39c6cef --- /dev/null +++ b/make-bsd.mk @@ -0,0 +1,156 @@ +# This requires GNU make, which is typically "gmake" on BSD systems + +INCLUDES= +DEFS= +LIBS= + +include objects.mk +OBJS+=osdep/BSDEthernetTap.o ext/http-parser/http_parser.o + +# Build with ZT_ENABLE_CLUSTER=1 to build with cluster support +ifeq ($(ZT_ENABLE_CLUSTER),1) + DEFS+=-DZT_ENABLE_CLUSTER +endif + +# "make debug" is a shortcut for this +ifeq ($(ZT_DEBUG),1) + DEFS+=-DZT_TRACE + CFLAGS+=-Wall -g -pthread $(INCLUDES) $(DEFS) + LDFLAGS+= + STRIP=echo + # The following line enables optimization for the crypto code, since + # C25519 in particular is almost UNUSABLE in heavy testing without it. +node/Salsa20.o node/SHA512.o node/C25519.o node/Poly1305.o: CFLAGS = -Wall -O2 -g -pthread $(INCLUDES) $(DEFS) +else + CFLAGS?=-O3 -fstack-protector + CFLAGS+=-Wall -fPIE -fvisibility=hidden -fstack-protector -pthread $(INCLUDES) -DNDEBUG $(DEFS) + LDFLAGS+=-pie -Wl,-z,relro,-z,now + STRIP=strip --strip-all +endif + +# Determine system build architecture from compiler target +CC_MACH=$(shell $(CC) -dumpmachine | cut -d '-' -f 1) +ZT_ARCHITECTURE=999 +ifeq ($(CC_MACH),x86_64) + ZT_ARCHITECTURE=2 + ZT_USE_X64_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),amd64) + ZT_ARCHITECTURE=2 + ZT_USE_X64_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),i386) + ZT_ARCHITECTURE=1 +endif +ifeq ($(CC_MACH),i686) + ZT_ARCHITECTURE=1 +endif +ifeq ($(CC_MACH),arm) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armel) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armhf) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv6) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv6zk) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv6kz) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv7) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),arm64) + ZT_ARCHITECTURE=4 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),aarch64) + ZT_ARCHITECTURE=4 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mipsel) + ZT_ARCHITECTURE=5 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mips) + ZT_ARCHITECTURE=5 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mips64) + ZT_ARCHITECTURE=6 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mips64el) + ZT_ARCHITECTURE=6 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif + +# Fail if system architecture could not be determined +ifeq ($(ZT_ARCHITECTURE),999) +ERR=$(error FATAL: architecture could not be determined from $(CC) -dumpmachine: $CC_MACH) +.PHONY: err +err: ; $(ERR) +endif + +# Build faster crypto on some targets +ifeq ($(ZT_USE_X64_ASM_SALSA2012),1) + override DEFS+=-DZT_USE_X64_ASM_SALSA2012 + override OBJS+=ext/x64-salsa2012-asm/salsa2012.o +endif +ifeq ($(ZT_USE_ARM32_NEON_ASM_SALSA2012),1) + override DEFS+=-DZT_USE_ARM32_NEON_ASM_SALSA2012 + override OBJS+=ext/arm32-neon-salsa2012-asm/salsa2012.o +endif + +override DEFS+=-DZT_BUILD_PLATFORM=$(ZT_BUILD_PLATFORM) -DZT_BUILD_ARCHITECTURE=$(ZT_ARCHITECTURE) -DZT_SOFTWARE_UPDATE_DEFAULT="\"disable\"" + +CXXFLAGS+=$(CFLAGS) -fno-rtti -std=c++11 #-D_GLIBCXX_USE_C99 -D_GLIBCXX_USE_C99_MATH -D_GLIBCXX_USE_C99_MATH_TR1 + +all: one + +one: $(OBJS) service/OneService.o one.o + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o zerotier-one $(OBJS) service/OneService.o one.o $(LIBS) + $(STRIP) zerotier-one + ln -sf zerotier-one zerotier-idtool + ln -sf zerotier-one zerotier-cli + +selftest: $(OBJS) selftest.o + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o zerotier-selftest selftest.o $(OBJS) $(LIBS) + $(STRIP) zerotier-selftest + +clean: + rm -rf *.o node/*.o controller/*.o osdep/*.o service/*.o ext/http-parser/*.o build-* zerotier-one zerotier-idtool zerotier-selftest zerotier-cli ZeroTierOneInstaller-* $(OBJS) + +debug: FORCE + make -j 4 ZT_DEBUG=1 + +install: one + rm -f /usr/local/sbin/zerotier-one + cp zerotier-one /usr/local/sbin + ln -sf /usr/local/sbin/zerotier-one /usr/local/sbin/zerotier-cli + ln -sf /usr/local/sbin/zerotier-one /usr/local/bin/zerotier-idtool + +uninstall: FORCE + rm -rf /usr/local/sbin/zerotier-one /usr/local/sbin/zerotier-cli /usr/local/bin/zerotier-idtool /var/db/zerotier-one/zerotier-one.port /var/db/zerotier-one/zerotier-one.pid /var/db/zerotier-one/iddb.d + +FORCE: diff --git a/make-linux.mk b/make-linux.mk new file mode 100644 index 0000000..87d29af --- /dev/null +++ b/make-linux.mk @@ -0,0 +1,274 @@ +# Automagically pick clang or gcc, with preference for clang +# This is only done if we have not overridden these with an environment or CLI variable +ifeq ($(origin CC),default) + CC=$(shell if [ -e /usr/bin/clang ]; then echo clang; else echo gcc; fi) +endif +ifeq ($(origin CXX),default) + CXX=$(shell if [ -e /usr/bin/clang++ ]; then echo clang++; else echo g++; fi) +endif + +INCLUDES?= +DEFS?=-D_FORTIFY_SOURCE=2 +LDLIBS?= +DESTDIR?= + +include objects.mk + +# Use bundled http-parser since distribution versions are NOT API-stable or compatible! +# Trying to use dynamically linked libhttp-parser causes tons of compatibility problems. +OBJS+=ext/http-parser/http_parser.o + +# Auto-detect miniupnpc and nat-pmp as well and use system libs if present, +# otherwise build into binary as done on Mac and Windows. +OBJS+=osdep/PortMapper.o +DEFS+=-DZT_USE_MINIUPNPC +MINIUPNPC_IS_NEW_ENOUGH=$(shell grep -sqr '.*define.*MINIUPNPC_VERSION.*"2.."' /usr/include/miniupnpc/miniupnpc.h && echo 1) +ifeq ($(MINIUPNPC_IS_NEW_ENOUGH),1) + DEFS+=-DZT_USE_SYSTEM_MINIUPNPC + LDLIBS+=-lminiupnpc +else + DEFS+=-DMINIUPNP_STATICLIB -DMINIUPNPC_SET_SOCKET_TIMEOUT -DMINIUPNPC_GET_SRC_ADDR -D_BSD_SOURCE -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 -DOS_STRING=\"Linux\" -DMINIUPNPC_VERSION_STRING=\"2.0\" -DUPNP_VERSION_STRING=\"UPnP/1.1\" -DENABLE_STRNATPMPERR + OBJS+=ext/miniupnpc/connecthostport.o ext/miniupnpc/igd_desc_parse.o ext/miniupnpc/minisoap.o ext/miniupnpc/minissdpc.o ext/miniupnpc/miniupnpc.o ext/miniupnpc/miniwget.o ext/miniupnpc/minixml.o ext/miniupnpc/portlistingparse.o ext/miniupnpc/receivedata.o ext/miniupnpc/upnpcommands.o ext/miniupnpc/upnpdev.o ext/miniupnpc/upnperrors.o ext/miniupnpc/upnpreplyparse.o +endif +ifeq ($(wildcard /usr/include/natpmp.h),) + OBJS+=ext/libnatpmp/natpmp.o ext/libnatpmp/getgateway.o +else + LDLIBS+=-lnatpmp + DEFS+=-DZT_USE_SYSTEM_NATPMP +endif + +ifeq ($(ZT_ENABLE_CLUSTER),1) + DEFS+=-DZT_ENABLE_CLUSTER +endif + +ifeq ($(ZT_SYNOLOGY), 1) + DEFS+=-D__SYNOLOGY__ +endif + +ifeq ($(ZT_TRACE),1) + DEFS+=-DZT_TRACE +endif + +ifeq ($(ZT_RULES_ENGINE_DEBUGGING),1) + DEFS+=-DZT_RULES_ENGINE_DEBUGGING +endif + +ifeq ($(ZT_DEBUG),1) + DEFS+=-DZT_TRACE + override CFLAGS+=-Wall -g -O -pthread $(INCLUDES) $(DEFS) + override CXXFLAGS+=-Wall -g -O -std=c++11 -pthread $(INCLUDES) $(DEFS) + override LDFLAGS+= + STRIP?=echo + # The following line enables optimization for the crypto code, since + # C25519 in particular is almost UNUSABLE in -O0 even on a 3ghz box! +node/Salsa20.o node/SHA512.o node/C25519.o node/Poly1305.o: CFLAGS = -Wall -O2 -g -pthread $(INCLUDES) $(DEFS) +else + CFLAGS?=-O3 -fstack-protector + override CFLAGS+=-Wall -fPIE -pthread $(INCLUDES) -DNDEBUG $(DEFS) + CXXFLAGS?=-O3 -fstack-protector + override CXXFLAGS+=-Wall -Wno-unused-result -Wreorder -fPIE -std=c++11 -pthread $(INCLUDES) -DNDEBUG $(DEFS) + override LDFLAGS+=-pie -Wl,-z,relro,-z,now + STRIP?=strip + STRIP+=--strip-all +endif + +# Uncomment for gprof profile build +#CFLAGS=-Wall -g -pg -pthread $(INCLUDES) $(DEFS) +#CXXFLAGS=-Wall -g -pg -pthread $(INCLUDES) $(DEFS) +#LDFLAGS= +#STRIP=echo + +# Determine system build architecture from compiler target +CC_MACH=$(shell $(CC) -dumpmachine | cut -d '-' -f 1) +ZT_ARCHITECTURE=999 +ifeq ($(CC_MACH),x86_64) + ZT_ARCHITECTURE=2 + ZT_USE_X64_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),amd64) + ZT_ARCHITECTURE=2 + ZT_USE_X64_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),i386) + ZT_ARCHITECTURE=1 +endif +ifeq ($(CC_MACH),i686) + ZT_ARCHITECTURE=1 +endif +ifeq ($(CC_MACH),arm) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armel) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armhf) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv6) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv6zk) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv6kz) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),armv7) + ZT_ARCHITECTURE=3 + override DEFS+=-DZT_NO_TYPE_PUNNING + ZT_USE_ARM32_NEON_ASM_SALSA2012=1 +endif +ifeq ($(CC_MACH),arm64) + ZT_ARCHITECTURE=4 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),aarch64) + ZT_ARCHITECTURE=4 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mipsel) + ZT_ARCHITECTURE=5 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mips) + ZT_ARCHITECTURE=5 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mips64) + ZT_ARCHITECTURE=6 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif +ifeq ($(CC_MACH),mips64el) + ZT_ARCHITECTURE=6 + override DEFS+=-DZT_NO_TYPE_PUNNING +endif + +# Fail if system architecture could not be determined +ifeq ($(ZT_ARCHITECTURE),999) +ERR=$(error FATAL: architecture could not be determined from $(CC) -dumpmachine: $CC_MACH) +.PHONY: err +err: ; $(ERR) +endif + +# Disable software updates by default on Linux since that is normally done with package management +override DEFS+=-DZT_BUILD_PLATFORM=1 -DZT_BUILD_ARCHITECTURE=$(ZT_ARCHITECTURE) -DZT_SOFTWARE_UPDATE_DEFAULT="\"disable\"" + +# Build faster crypto on some targets +ifeq ($(ZT_USE_X64_ASM_SALSA2012),1) + override DEFS+=-DZT_USE_X64_ASM_SALSA2012 + override OBJS+=ext/x64-salsa2012-asm/salsa2012.o +endif +ifeq ($(ZT_USE_ARM32_NEON_ASM_SALSA2012),1) + override DEFS+=-DZT_USE_ARM32_NEON_ASM_SALSA2012 + override OBJS+=ext/arm32-neon-salsa2012-asm/salsa2012.o +endif + +# Static builds, which are currently done for a number of Linux targets +ifeq ($(ZT_STATIC),1) + override LDFLAGS+=-static + ifeq ($(ZT_ARCHITECTURE),3) + ifeq ($(ZT_ARM_SOFTFLOAT),1) + override CFLAGS+=-march=armv5te -mfloat-abi=soft -msoft-float -mno-unaligned-access -marm + override CXXFLAGS+=-march=armv5te -mfloat-abi=soft -msoft-float -mno-unaligned-access -marm + else + override CFLAGS+=-march=armv6kz -mcpu=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -mno-unaligned-access -marm + override CXXFLAGS+=-march=armv6kz -mcpu=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -mno-unaligned-access -marm + endif + endif +endif + +all: one + +one: $(OBJS) service/OneService.o one.o osdep/LinuxEthernetTap.o + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o zerotier-one $(OBJS) service/OneService.o one.o osdep/LinuxEthernetTap.o $(LDLIBS) + $(STRIP) zerotier-one + ln -sf zerotier-one zerotier-idtool + ln -sf zerotier-one zerotier-cli + +selftest: $(OBJS) selftest.o + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o zerotier-selftest selftest.o $(OBJS) $(LDLIBS) + $(STRIP) zerotier-selftest + +manpages: FORCE + cd doc ; ./build.sh + +doc: manpages + +clean: FORCE + rm -rf *.so *.o node/*.o controller/*.o osdep/*.o service/*.o ext/http-parser/*.o ext/miniupnpc/*.o ext/libnatpmp/*.o $(OBJS) zerotier-one zerotier-idtool zerotier-cli zerotier-selftest build-* ZeroTierOneInstaller-* *.deb *.rpm .depend debian/files debian/zerotier-one*.debhelper debian/zerotier-one.substvars debian/*.log debian/zerotier-one doc/node_modules + +distclean: clean + +realclean: distclean + +debug: FORCE + make ZT_DEBUG=1 one + make ZT_DEBUG=1 selftest + +# Note: keep the symlinks in /var/lib/zerotier-one to the binaries since these +# provide backward compatibility with old releases where the binaries actually +# lived here. Folks got scripts. + +install: FORCE + mkdir -p $(DESTDIR)/usr/sbin + rm -f $(DESTDIR)/usr/sbin/zerotier-one + cp -f zerotier-one $(DESTDIR)/usr/sbin/zerotier-one + rm -f $(DESTDIR)/usr/sbin/zerotier-cli + rm -f $(DESTDIR)/usr/sbin/zerotier-idtool + ln -s zerotier-one $(DESTDIR)/usr/sbin/zerotier-cli + ln -s zerotier-one $(DESTDIR)/usr/sbin/zerotier-idtool + mkdir -p $(DESTDIR)/var/lib/zerotier-one + rm -f $(DESTDIR)/var/lib/zerotier-one/zerotier-one + rm -f $(DESTDIR)/var/lib/zerotier-one/zerotier-cli + rm -f $(DESTDIR)/var/lib/zerotier-one/zerotier-idtool + ln -s ../../../usr/sbin/zerotier-one $(DESTDIR)/var/lib/zerotier-one/zerotier-one + ln -s ../../../usr/sbin/zerotier-one $(DESTDIR)/var/lib/zerotier-one/zerotier-cli + ln -s ../../../usr/sbin/zerotier-one $(DESTDIR)/var/lib/zerotier-one/zerotier-idtool + mkdir -p $(DESTDIR)/usr/share/man/man8 + rm -f $(DESTDIR)/usr/share/man/man8/zerotier-one.8.gz + cat doc/zerotier-one.8 | gzip -9 >$(DESTDIR)/usr/share/man/man8/zerotier-one.8.gz + mkdir -p $(DESTDIR)/usr/share/man/man1 + rm -f $(DESTDIR)/usr/share/man/man1/zerotier-idtool.1.gz + rm -f $(DESTDIR)/usr/share/man/man1/zerotier-cli.1.gz + cat doc/zerotier-cli.1 | gzip -9 >$(DESTDIR)/usr/share/man/man1/zerotier-cli.1.gz + cat doc/zerotier-idtool.1 | gzip -9 >$(DESTDIR)/usr/share/man/man1/zerotier-idtool.1.gz + +# Uninstall preserves identity.public and identity.secret since the user might +# want to save these. These are your ZeroTier address. + +uninstall: FORCE + rm -f $(DESTDIR)/var/lib/zerotier-one/zerotier-one + rm -f $(DESTDIR)/var/lib/zerotier-one/zerotier-cli + rm -f $(DESTDIR)/var/lib/zerotier-one/zerotier-idtool + rm -f $(DESTDIR)/usr/sbin/zerotier-cli + rm -f $(DESTDIR)/usr/sbin/zerotier-idtool + rm -f $(DESTDIR)/usr/sbin/zerotier-one + rm -rf $(DESTDIR)/var/lib/zerotier-one/iddb.d + rm -rf $(DESTDIR)/var/lib/zerotier-one/updates.d + rm -rf $(DESTDIR)/var/lib/zerotier-one/networks.d + rm -f $(DESTDIR)/var/lib/zerotier-one/zerotier-one.port + rm -f $(DESTDIR)/usr/share/man/man8/zerotier-one.8.gz + rm -f $(DESTDIR)/usr/share/man/man1/zerotier-idtool.1.gz + rm -f $(DESTDIR)/usr/share/man/man1/zerotier-cli.1.gz + +# These are just for convenience for building Linux packages + +debian: FORCE + debuild -I -i -us -uc -nc -b + +redhat: FORCE + rpmbuild -ba zerotier-one.spec + +FORCE: diff --git a/make-mac.mk b/make-mac.mk new file mode 100644 index 0000000..6676f45 --- /dev/null +++ b/make-mac.mk @@ -0,0 +1,118 @@ +CC=clang +CXX=clang++ +INCLUDES= +DEFS= +LIBS= +ARCH_FLAGS= +CODESIGN=echo +PRODUCTSIGN=echo +CODESIGN_APP_CERT= +CODESIGN_INSTALLER_CERT= + +ZT_BUILD_PLATFORM=3 +ZT_BUILD_ARCHITECTURE=2 +ZT_VERSION_MAJOR=$(shell cat version.h | grep -F VERSION_MAJOR | cut -d ' ' -f 3) +ZT_VERSION_MINOR=$(shell cat version.h | grep -F VERSION_MINOR | cut -d ' ' -f 3) +ZT_VERSION_REV=$(shell cat version.h | grep -F VERSION_REVISION | cut -d ' ' -f 3) +ZT_VERSION_BUILD=$(shell cat version.h | grep -F VERSION_BUILD | cut -d ' ' -f 3) + +DEFS+=-DZT_BUILD_PLATFORM=$(ZT_BUILD_PLATFORM) -DZT_BUILD_ARCHITECTURE=$(ZT_BUILD_ARCHITECTURE) + +include objects.mk +OBJS+=osdep/OSXEthernetTap.o ext/http-parser/http_parser.o + +# Official releases are signed with our Apple cert and apply software updates by default +ifeq ($(ZT_OFFICIAL_RELEASE),1) + DEFS+=-DZT_SOFTWARE_UPDATE_DEFAULT="\"apply\"" + ZT_USE_MINIUPNPC=1 + CODESIGN=codesign + PRODUCTSIGN=productsign + CODESIGN_APP_CERT="Developer ID Application: ZeroTier, Inc (8ZD9JUCZ4V)" + CODESIGN_INSTALLER_CERT="Developer ID Installer: ZeroTier, Inc (8ZD9JUCZ4V)" +else + DEFS+=-DZT_SOFTWARE_UPDATE_DEFAULT="\"download\"" +endif + +ifeq ($(ZT_ENABLE_CLUSTER),1) + DEFS+=-DZT_ENABLE_CLUSTER +endif + +# Use fast ASM Salsa20/12 for x64 processors +DEFS+=-DZT_USE_X64_ASM_SALSA2012 +OBJS+=ext/x64-salsa2012-asm/salsa2012.o + +# Build miniupnpc and nat-pmp as included libraries -- extra defs are required for these sources +DEFS+=-DMACOSX -DZT_USE_MINIUPNPC -DMINIUPNP_STATICLIB -D_DARWIN_C_SOURCE -DMINIUPNPC_SET_SOCKET_TIMEOUT -DMINIUPNPC_GET_SRC_ADDR -D_BSD_SOURCE -D_DEFAULT_SOURCE -DOS_STRING=\"Darwin/15.0.0\" -DMINIUPNPC_VERSION_STRING=\"2.0\" -DUPNP_VERSION_STRING=\"UPnP/1.1\" -DENABLE_STRNATPMPERR +OBJS+=ext/libnatpmp/natpmp.o ext/libnatpmp/getgateway.o ext/miniupnpc/connecthostport.o ext/miniupnpc/igd_desc_parse.o ext/miniupnpc/minisoap.o ext/miniupnpc/minissdpc.o ext/miniupnpc/miniupnpc.o ext/miniupnpc/miniwget.o ext/miniupnpc/minixml.o ext/miniupnpc/portlistingparse.o ext/miniupnpc/receivedata.o ext/miniupnpc/upnpcommands.o ext/miniupnpc/upnpdev.o ext/miniupnpc/upnperrors.o ext/miniupnpc/upnpreplyparse.o osdep/PortMapper.o + +# Debug mode -- dump trace output, build binary with -g +ifeq ($(ZT_DEBUG),1) + DEFS+=-DZT_TRACE + CFLAGS+=-Wall -g -pthread $(INCLUDES) $(DEFS) + STRIP=echo + # The following line enables optimization for the crypto code, since + # C25519 in particular is almost UNUSABLE in heavy testing without it. +node/Salsa20.o node/SHA512.o node/C25519.o node/Poly1305.o: CFLAGS = -Wall -O2 -g -pthread $(INCLUDES) $(DEFS) +else + CFLAGS?=-Ofast -fstack-protector-strong + CFLAGS+=$(ARCH_FLAGS) -Wall -flto -fPIE -pthread -mmacosx-version-min=10.7 -DNDEBUG -Wno-unused-private-field $(INCLUDES) $(DEFS) + STRIP=strip +endif + +CXXFLAGS=$(CFLAGS) -std=c++11 -stdlib=libc++ + +all: one macui + +ext/x64-salsa2012-asm/salsa2012.o: + $(CC) $(CFLAGS) -c ext/x64-salsa2012-asm/salsa2012.s -o ext/x64-salsa2012-asm/salsa2012.o + +one: $(OBJS) service/OneService.o one.o + $(CXX) $(CXXFLAGS) -o zerotier-one $(OBJS) service/OneService.o one.o $(LIBS) + $(STRIP) zerotier-one + ln -sf zerotier-one zerotier-idtool + ln -sf zerotier-one zerotier-cli + $(CODESIGN) -f -s $(CODESIGN_APP_CERT) zerotier-one + +macui: FORCE + cd macui && xcodebuild -target "ZeroTier One" -configuration Release + $(CODESIGN) -f -s $(CODESIGN_APP_CERT) "macui/build/Release/ZeroTier One.app" + +#cli: FORCE +# $(CXX) $(CXXFLAGS) -o zerotier cli/zerotier.cpp osdep/OSUtils.cpp node/InetAddress.cpp node/Utils.cpp node/Salsa20.cpp node/Identity.cpp node/SHA512.cpp node/C25519.cpp -lcurl +# $(STRIP) zerotier + +selftest: $(OBJS) selftest.o + $(CXX) $(CXXFLAGS) -o zerotier-selftest selftest.o $(OBJS) $(LIBS) + $(STRIP) zerotier-selftest + +# Requires Packages: http://s.sudre.free.fr/Software/Packages/about.html +mac-dist-pkg: FORCE + packagesbuild "ext/installfiles/mac/ZeroTier One.pkgproj" + rm -f "ZeroTier One Signed.pkg" + $(PRODUCTSIGN) --sign $(CODESIGN_INSTALLER_CERT) "ZeroTier One.pkg" "ZeroTier One Signed.pkg" + if [ -f "ZeroTier One Signed.pkg" ]; then mv -f "ZeroTier One Signed.pkg" "ZeroTier One.pkg"; fi + rm -f zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_* + cat ext/installfiles/mac-update/updater.tmpl.sh "ZeroTier One.pkg" >zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_$(ZT_VERSION_MAJOR).$(ZT_VERSION_MINOR).$(ZT_VERSION_REV)_$(ZT_VERSION_BUILD).exe + +# For ZeroTier, Inc. to build official signed packages +official: FORCE + make clean + make ZT_OFFICIAL_RELEASE=1 -j 4 one + make ZT_OFFICIAL_RELEASE=1 macui + make ZT_OFFICIAL_RELEASE=1 mac-dist-pkg + +clean: + rm -rf *.dSYM build-* *.pkg *.dmg *.o node/*.o controller/*.o service/*.o osdep/*.o ext/http-parser/*.o $(OBJS) zerotier-one zerotier-idtool zerotier-selftest zerotier-cli zerotier mkworld doc/node_modules macui/build zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_* + +distclean: clean + +realclean: clean + +# For those building from source -- installs signed binary tap driver in system ZT home +install-mac-tap: FORCE + mkdir -p /Library/Application\ Support/ZeroTier/One + rm -rf /Library/Application\ Support/ZeroTier/One/tap.kext + cp -R ext/bin/tap-mac/tap.kext /Library/Application\ Support/ZeroTier/One + chown -R root:wheel /Library/Application\ Support/ZeroTier/One/tap.kext + +FORCE: diff --git a/node/Address.hpp b/node/Address.hpp new file mode 100644 index 0000000..4a5883b --- /dev/null +++ b/node/Address.hpp @@ -0,0 +1,202 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_ADDRESS_HPP +#define ZT_ADDRESS_HPP + +#include +#include +#include +#include + +#include + +#include "Constants.hpp" +#include "Utils.hpp" +#include "Buffer.hpp" + +namespace ZeroTier { + +/** + * A ZeroTier address + */ +class Address +{ +public: + Address() : _a(0) {} + Address(const Address &a) : _a(a._a) {} + Address(uint64_t a) : _a(a & 0xffffffffffULL) {} + + /** + * @param bits Raw address -- 5 bytes, big-endian byte order + * @param len Length of array + */ + Address(const void *bits,unsigned int len) + { + setTo(bits,len); + } + + inline Address &operator=(const Address &a) + { + _a = a._a; + return *this; + } + + inline Address &operator=(const uint64_t a) + { + _a = (a & 0xffffffffffULL); + return *this; + } + + /** + * @param bits Raw address -- 5 bytes, big-endian byte order + * @param len Length of array + */ + inline void setTo(const void *bits,unsigned int len) + { + if (len < ZT_ADDRESS_LENGTH) { + _a = 0; + return; + } + const unsigned char *b = (const unsigned char *)bits; + uint64_t a = ((uint64_t)*b++) << 32; + a |= ((uint64_t)*b++) << 24; + a |= ((uint64_t)*b++) << 16; + a |= ((uint64_t)*b++) << 8; + a |= ((uint64_t)*b); + _a = a; + } + + /** + * @param bits Buffer to hold 5-byte address in big-endian byte order + * @param len Length of array + */ + inline void copyTo(void *bits,unsigned int len) const + { + if (len < ZT_ADDRESS_LENGTH) + return; + unsigned char *b = (unsigned char *)bits; + *(b++) = (unsigned char)((_a >> 32) & 0xff); + *(b++) = (unsigned char)((_a >> 24) & 0xff); + *(b++) = (unsigned char)((_a >> 16) & 0xff); + *(b++) = (unsigned char)((_a >> 8) & 0xff); + *b = (unsigned char)(_a & 0xff); + } + + /** + * Append to a buffer in big-endian byte order + * + * @param b Buffer to append to + */ + template + inline void appendTo(Buffer &b) const + { + unsigned char *p = (unsigned char *)b.appendField(ZT_ADDRESS_LENGTH); + *(p++) = (unsigned char)((_a >> 32) & 0xff); + *(p++) = (unsigned char)((_a >> 24) & 0xff); + *(p++) = (unsigned char)((_a >> 16) & 0xff); + *(p++) = (unsigned char)((_a >> 8) & 0xff); + *p = (unsigned char)(_a & 0xff); + } + + /** + * @return Integer containing address (0 to 2^40) + */ + inline uint64_t toInt() const + { + return _a; + } + + /** + * @return Hash code for use with Hashtable + */ + inline unsigned long hashCode() const + { + return (unsigned long)_a; + } + + /** + * @return Hexadecimal string + */ + inline std::string toString() const + { + char buf[16]; + Utils::snprintf(buf,sizeof(buf),"%.10llx",(unsigned long long)_a); + return std::string(buf); + }; + + /** + * @param buf Buffer to fill + * @param len Length of buffer + */ + inline void toString(char *buf,unsigned int len) const + { + Utils::snprintf(buf,len,"%.10llx",(unsigned long long)_a); + } + + /** + * @return True if this address is not zero + */ + inline operator bool() const { return (_a != 0); } + + /** + * Set to null/zero + */ + inline void zero() { _a = 0; } + + /** + * Check if this address is reserved + * + * The all-zero null address and any address beginning with 0xff are + * reserved. (0xff is reserved for future use to designate possibly + * longer addresses, addresses based on IPv6 innards, etc.) + * + * @return True if address is reserved and may not be used + */ + inline bool isReserved() const + { + return ((!_a)||((_a >> 32) == ZT_ADDRESS_RESERVED_PREFIX)); + } + + /** + * @param i Value from 0 to 4 (inclusive) + * @return Byte at said position (address interpreted in big-endian order) + */ + inline unsigned char operator[](unsigned int i) const { return (unsigned char)((_a >> (32 - (i * 8))) & 0xff); } + + inline bool operator==(const uint64_t &a) const { return (_a == (a & 0xffffffffffULL)); } + inline bool operator!=(const uint64_t &a) const { return (_a != (a & 0xffffffffffULL)); } + inline bool operator>(const uint64_t &a) const { return (_a > (a & 0xffffffffffULL)); } + inline bool operator<(const uint64_t &a) const { return (_a < (a & 0xffffffffffULL)); } + inline bool operator>=(const uint64_t &a) const { return (_a >= (a & 0xffffffffffULL)); } + inline bool operator<=(const uint64_t &a) const { return (_a <= (a & 0xffffffffffULL)); } + + inline bool operator==(const Address &a) const { return (_a == a._a); } + inline bool operator!=(const Address &a) const { return (_a != a._a); } + inline bool operator>(const Address &a) const { return (_a > a._a); } + inline bool operator<(const Address &a) const { return (_a < a._a); } + inline bool operator>=(const Address &a) const { return (_a >= a._a); } + inline bool operator<=(const Address &a) const { return (_a <= a._a); } + +private: + uint64_t _a; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Array.hpp b/node/Array.hpp new file mode 100644 index 0000000..19b29eb --- /dev/null +++ b/node/Array.hpp @@ -0,0 +1,107 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_ARRAY_HPP +#define ZT_ARRAY_HPP + +#include +#include + +namespace ZeroTier { + +/** + * Static array -- a simple thing that's belonged in STL since the time of the dinosaurs + */ +template +class Array +{ +public: + Array() throw() {} + + Array(const Array &a) + { + for(std::size_t i=0;i reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + inline iterator begin() throw() { return data; } + inline iterator end() throw() { return &(data[S]); } + inline const_iterator begin() const throw() { return data; } + inline const_iterator end() const throw() { return &(data[S]); } + + inline reverse_iterator rbegin() throw() { return reverse_iterator(begin()); } + inline reverse_iterator rend() throw() { return reverse_iterator(end()); } + inline const_reverse_iterator rbegin() const throw() { return const_reverse_iterator(begin()); } + inline const_reverse_iterator rend() const throw() { return const_reverse_iterator(end()); } + + inline std::size_t size() const throw() { return S; } + inline std::size_t max_size() const throw() { return S; } + + inline reference operator[](const std::size_t n) throw() { return data[n]; } + inline const_reference operator[](const std::size_t n) const throw() { return data[n]; } + + inline reference front() throw() { return data[0]; } + inline const_reference front() const throw() { return data[0]; } + inline reference back() throw() { return data[S-1]; } + inline const_reference back() const throw() { return data[S-1]; } + + inline bool operator==(const Array &k) const throw() + { + for(unsigned long i=0;i(const Array &k) const throw() { return (k < *this); } + inline bool operator<=(const Array &k) const throw() { return !(k < *this); } + inline bool operator>=(const Array &k) const throw() { return !(*this < k); } + + T data[S]; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/AtomicCounter.hpp b/node/AtomicCounter.hpp new file mode 100644 index 0000000..a0f29ba --- /dev/null +++ b/node/AtomicCounter.hpp @@ -0,0 +1,70 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_ATOMICCOUNTER_HPP +#define ZT_ATOMICCOUNTER_HPP + +#include "Constants.hpp" +#include "NonCopyable.hpp" + +#ifndef __GNUC__ +#include +#endif + +namespace ZeroTier { + +/** + * Simple atomic counter supporting increment and decrement + */ +class AtomicCounter : NonCopyable +{ +public: + AtomicCounter() + { + _v = 0; + } + + inline int operator++() + { +#ifdef __GNUC__ + return __sync_add_and_fetch(&_v,1); +#else + return ++_v; +#endif + } + + inline int operator--() + { +#ifdef __GNUC__ + return __sync_sub_and_fetch(&_v,1); +#else + return --_v; +#endif + } + +private: +#ifdef __GNUC__ + int _v; +#else + std::atomic_int _v; +#endif +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Buffer.hpp b/node/Buffer.hpp new file mode 100644 index 0000000..37f39e7 --- /dev/null +++ b/node/Buffer.hpp @@ -0,0 +1,496 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_BUFFER_HPP +#define ZT_BUFFER_HPP + +#include +#include + +#include +#include +#include +#include + +#include "Constants.hpp" +#include "Utils.hpp" + +#if defined(__GNUC__) && (!defined(ZT_NO_TYPE_PUNNING)) +#define ZT_VAR_MAY_ALIAS __attribute__((__may_alias__)) +#else +#define ZT_VAR_MAY_ALIAS +#endif + +namespace ZeroTier { + +/** + * A variable length but statically allocated buffer + * + * Bounds-checking is done everywhere, since this is used in security + * critical code. This supports construction and assignment from buffers + * of differing capacities, provided the data actually in them fits. + * It throws std::out_of_range on any boundary violation. + * + * The at(), append(), etc. methods encode integers larger than 8-bit in + * big-endian (network) byte order. + * + * @tparam C Total capacity + */ +template +class Buffer +{ + // I love me! + template friend class Buffer; + +public: + // STL container idioms + typedef unsigned char value_type; + typedef unsigned char * pointer; + typedef const char * const_pointer; + typedef char & reference; + typedef const char & const_reference; + typedef char * iterator; + typedef const char * const_iterator; + typedef unsigned int size_type; + typedef int difference_type; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + inline iterator begin() { return _b; } + inline iterator end() { return (_b + _l); } + inline const_iterator begin() const { return _b; } + inline const_iterator end() const { return (_b + _l); } + inline reverse_iterator rbegin() { return reverse_iterator(begin()); } + inline reverse_iterator rend() { return reverse_iterator(end()); } + inline const_reverse_iterator rbegin() const { return const_reverse_iterator(begin()); } + inline const_reverse_iterator rend() const { return const_reverse_iterator(end()); } + + Buffer() : + _l(0) + { + } + + Buffer(unsigned int l) + throw(std::out_of_range) + { + if (l > C) + throw std::out_of_range("Buffer: construct with size larger than capacity"); + _l = l; + } + + template + Buffer(const Buffer &b) + throw(std::out_of_range) + { + *this = b; + } + + Buffer(const void *b,unsigned int l) + throw(std::out_of_range) + { + copyFrom(b,l); + } + + Buffer(const std::string &s) + throw(std::out_of_range) + { + copyFrom(s.data(),s.length()); + } + + template + inline Buffer &operator=(const Buffer &b) + throw(std::out_of_range) + { + if (b._l > C) + throw std::out_of_range("Buffer: assignment from buffer larger than capacity"); + memcpy(_b,b._b,_l = b._l); + return *this; + } + + inline Buffer &operator=(const std::string &s) + throw(std::out_of_range) + { + copyFrom(s.data(),s.length()); + return *this; + } + + inline void copyFrom(const void *b,unsigned int l) + throw(std::out_of_range) + { + if (l > C) + throw std::out_of_range("Buffer: set from C array larger than capacity"); + _l = l; + memcpy(_b,b,l); + } + + unsigned char operator[](const unsigned int i) const + throw(std::out_of_range) + { + if (i >= _l) + throw std::out_of_range("Buffer: [] beyond end of data"); + return (unsigned char)_b[i]; + } + + unsigned char &operator[](const unsigned int i) + throw(std::out_of_range) + { + if (i >= _l) + throw std::out_of_range("Buffer: [] beyond end of data"); + return ((unsigned char *)_b)[i]; + } + + /** + * Get a raw pointer to a field with bounds checking + * + * This isn't perfectly safe in that the caller could still overflow + * the pointer, but its use provides both a sanity check and + * documentation / reminder to the calling code to treat the returned + * pointer as being of size [l]. + * + * @param i Index of field in buffer + * @param l Length of field in bytes + * @return Pointer to field data + * @throws std::out_of_range Field extends beyond data size + */ + unsigned char *field(unsigned int i,unsigned int l) + throw(std::out_of_range) + { + if ((i + l) > _l) + throw std::out_of_range("Buffer: field() beyond end of data"); + return (unsigned char *)(_b + i); + } + const unsigned char *field(unsigned int i,unsigned int l) const + throw(std::out_of_range) + { + if ((i + l) > _l) + throw std::out_of_range("Buffer: field() beyond end of data"); + return (const unsigned char *)(_b + i); + } + + /** + * Place a primitive integer value at a given position + * + * @param i Index to place value + * @param v Value + * @tparam T Integer type (e.g. uint16_t, int64_t) + */ + template + inline void setAt(unsigned int i,const T v) + throw(std::out_of_range) + { + if ((i + sizeof(T)) > _l) + throw std::out_of_range("Buffer: setAt() beyond end of data"); +#ifdef ZT_NO_TYPE_PUNNING + uint8_t *p = reinterpret_cast(_b + i); + for(unsigned int x=1;x<=sizeof(T);++x) + *(p++) = (uint8_t)(v >> (8 * (sizeof(T) - x))); +#else + T *const ZT_VAR_MAY_ALIAS p = reinterpret_cast(_b + i); + *p = Utils::hton(v); +#endif + } + + /** + * Get a primitive integer value at a given position + * + * @param i Index to get integer + * @tparam T Integer type (e.g. uint16_t, int64_t) + * @return Integer value + */ + template + inline T at(unsigned int i) const + throw(std::out_of_range) + { + if ((i + sizeof(T)) > _l) + throw std::out_of_range("Buffer: at() beyond end of data"); +#ifdef ZT_NO_TYPE_PUNNING + T v = 0; + const uint8_t *p = reinterpret_cast(_b + i); + for(unsigned int x=0;x(_b + i); + return Utils::ntoh(*p); +#endif + } + + /** + * Append an integer type to this buffer + * + * @param v Value to append + * @tparam T Integer type (e.g. uint16_t, int64_t) + * @throws std::out_of_range Attempt to append beyond capacity + */ + template + inline void append(const T v) + throw(std::out_of_range) + { + if ((_l + sizeof(T)) > C) + throw std::out_of_range("Buffer: append beyond capacity"); +#ifdef ZT_NO_TYPE_PUNNING + uint8_t *p = reinterpret_cast(_b + _l); + for(unsigned int x=1;x<=sizeof(T);++x) + *(p++) = (uint8_t)(v >> (8 * (sizeof(T) - x))); +#else + T *const ZT_VAR_MAY_ALIAS p = reinterpret_cast(_b + _l); + *p = Utils::hton(v); +#endif + _l += sizeof(T); + } + + /** + * Append a run of bytes + * + * @param c Character value to append + * @param n Number of times to append + * @throws std::out_of_range Attempt to append beyond capacity + */ + inline void append(unsigned char c,unsigned int n) + throw(std::out_of_range) + { + if ((_l + n) > C) + throw std::out_of_range("Buffer: append beyond capacity"); + for(unsigned int i=0;i C) + throw std::out_of_range("Buffer: append beyond capacity"); + memcpy(_b + _l,b,l); + _l += l; + } + + /** + * Append a string + * + * @param s String to append + * @throws std::out_of_range Attempt to append beyond capacity + */ + inline void append(const std::string &s) + throw(std::out_of_range) + { + append(s.data(),(unsigned int)s.length()); + } + + /** + * Append a C string including null termination byte + * + * @param s C string + * @throws std::out_of_range Attempt to append beyond capacity + */ + inline void appendCString(const char *s) + throw(std::out_of_range) + { + for(;;) { + if (_l >= C) + throw std::out_of_range("Buffer: append beyond capacity"); + if (!(_b[_l++] = *(s++))) + break; + } + } + + /** + * Append a buffer + * + * @param b Buffer to append + * @tparam C2 Capacity of second buffer (typically inferred) + * @throws std::out_of_range Attempt to append beyond capacity + */ + template + inline void append(const Buffer &b) + throw(std::out_of_range) + { + append(b._b,b._l); + } + + /** + * Increment size and return pointer to field of specified size + * + * Nothing is actually written to the memory. This is a shortcut + * for addSize() followed by field() to reference the previous + * position and the new size. + * + * @param l Length of field to append + * @return Pointer to beginning of appended field of length 'l' + */ + inline char *appendField(unsigned int l) + throw(std::out_of_range) + { + if ((_l + l) > C) + throw std::out_of_range("Buffer: append beyond capacity"); + char *r = _b + _l; + _l += l; + return r; + } + + /** + * Increment size by a given number of bytes + * + * The contents of new space are undefined. + * + * @param i Bytes to increment + * @throws std::out_of_range Capacity exceeded + */ + inline void addSize(unsigned int i) + throw(std::out_of_range) + { + if ((i + _l) > C) + throw std::out_of_range("Buffer: setSize to larger than capacity"); + _l += i; + } + + /** + * Set size of data in buffer + * + * The contents of new space are undefined. + * + * @param i New size + * @throws std::out_of_range Size larger than capacity + */ + inline void setSize(const unsigned int i) + throw(std::out_of_range) + { + if (i > C) + throw std::out_of_range("Buffer: setSize to larger than capacity"); + _l = i; + } + + /** + * Move everything after 'at' to the buffer's front and truncate + * + * @param at Truncate before this position + * @throw std::out_of_range Position is beyond size of buffer + */ + inline void behead(const unsigned int at) + throw(std::out_of_range) + { + if (!at) + return; + if (at > _l) + throw std::out_of_range("Buffer: behead() beyond capacity"); + ::memmove(_b,_b + at,_l -= at); + } + + /** + * Erase something from the middle of the buffer + * + * @param start Starting position + * @param length Length of block to erase + * @throw std::out_of_range Position plus length is beyond size of buffer + */ + inline void erase(const unsigned int at,const unsigned int length) + throw(std::out_of_range) + { + const unsigned int endr = at + length; + if (endr > _l) + throw std::out_of_range("Buffer: erase() range beyond end of buffer"); + ::memmove(_b + at,_b + endr,_l - endr); + _l -= length; + } + + /** + * Set buffer data length to zero + */ + inline void clear() { _l = 0; } + + /** + * Zero buffer up to size() + */ + inline void zero() { memset(_b,0,_l); } + + /** + * Zero unused capacity area + */ + inline void zeroUnused() { memset(_b + _l,0,C - _l); } + + /** + * Unconditionally and securely zero buffer's underlying memory + */ + inline void burn() { Utils::burn(_b,sizeof(_b)); } + + /** + * @return Constant pointer to data in buffer + */ + inline const void *data() const { return _b; } + + /** + * @return Non-constant pointer to data in buffer + */ + inline void *unsafeData() { return _b; } + + /** + * @return Size of data in buffer + */ + inline unsigned int size() const { return _l; } + + /** + * @return Capacity of buffer + */ + inline unsigned int capacity() const { return C; } + + template + inline bool operator==(const Buffer &b) const + { + return ((_l == b._l)&&(!memcmp(_b,b._b,_l))); + } + template + inline bool operator!=(const Buffer &b) const + { + return ((_l != b._l)||(memcmp(_b,b._b,_l))); + } + template + inline bool operator<(const Buffer &b) const + { + return (memcmp(_b,b._b,std::min(_l,b._l)) < 0); + } + template + inline bool operator>(const Buffer &b) const + { + return (b < *this); + } + template + inline bool operator<=(const Buffer &b) const + { + return !(b < *this); + } + template + inline bool operator>=(const Buffer &b) const + { + return !(*this < b); + } + +private: + unsigned int _l; + char ZT_VAR_MAY_ALIAS _b[C]; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/C25519.cpp b/node/C25519.cpp new file mode 100644 index 0000000..e9ffecc --- /dev/null +++ b/node/C25519.cpp @@ -0,0 +1,2398 @@ +// Code taken from NaCl by D. J. Bernstein and others + +/* +Matthew Dempsky +Public domain. +Derived from public domain code by D. J. Bernstein. +*/ + +// Modified very slightly for ZeroTier One by Adam Ierymenko +// (no functional changes) + +#include +#include +#include + +#include "Constants.hpp" +#include "C25519.hpp" +#include "SHA512.hpp" +#include "Buffer.hpp" + +#ifdef __WINDOWS__ +#pragma warning(disable: 4146) +#endif + +namespace ZeroTier { + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +#define crypto_int32 int32_t +#define crypto_uint32 uint32_t +#define crypto_int64 int64_t +#define crypto_uint64 uint64_t +#define crypto_hash_sha512_BYTES 64 + +static inline void add(unsigned int out[32],const unsigned int a[32],const unsigned int b[32]) +{ + unsigned int j; + unsigned int u; + u = 0; + for (j = 0;j < 31;++j) { u += a[j] + b[j]; out[j] = u & 255; u >>= 8; } + u += a[31] + b[31]; out[31] = u; +} + +static inline void sub(unsigned int out[32],const unsigned int a[32],const unsigned int b[32]) +{ + unsigned int j; + unsigned int u; + u = 218; + for (j = 0;j < 31;++j) { + u += a[j] + 65280 - b[j]; + out[j] = u & 255; + u >>= 8; + } + u += a[31] - b[31]; + out[31] = u; +} + +static inline void squeeze(unsigned int a[32]) +{ + unsigned int j; + unsigned int u; + u = 0; + for (j = 0;j < 31;++j) { u += a[j]; a[j] = u & 255; u >>= 8; } + u += a[31]; a[31] = u & 127; + u = 19 * (u >> 7); + for (j = 0;j < 31;++j) { u += a[j]; a[j] = u & 255; u >>= 8; } + u += a[31]; a[31] = u; +} + +static const unsigned int minusp[32] = { + 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128 +} ; + +static inline void freeze(unsigned int a[32]) +{ + unsigned int aorig[32]; + unsigned int j; + unsigned int negative; + + for (j = 0;j < 32;++j) aorig[j] = a[j]; + add(a,a,minusp); + negative = -((a[31] >> 7) & 1); + for (j = 0;j < 32;++j) a[j] ^= negative & (aorig[j] ^ a[j]); +} + +static inline void mult(unsigned int out[32],const unsigned int a[32],const unsigned int b[32]) +{ + unsigned int i; + unsigned int j; + unsigned int u; + + for (i = 0;i < 32;++i) { + u = 0; + for (j = 0;j <= i;++j) u += a[j] * b[i - j]; + for (j = i + 1;j < 32;++j) u += 38 * a[j] * b[i + 32 - j]; + out[i] = u; + } + squeeze(out); +} + +static inline void mult121665(unsigned int out[32],const unsigned int a[32]) +{ + unsigned int j; + unsigned int u; + + u = 0; + for (j = 0;j < 31;++j) { u += 121665 * a[j]; out[j] = u & 255; u >>= 8; } + u += 121665 * a[31]; out[31] = u & 127; + u = 19 * (u >> 7); + for (j = 0;j < 31;++j) { u += out[j]; out[j] = u & 255; u >>= 8; } + u += out[j]; out[j] = u; +} + +static inline void square(unsigned int out[32],const unsigned int a[32]) +{ + unsigned int i; + unsigned int j; + unsigned int u; + + for (i = 0;i < 32;++i) { + u = 0; + for (j = 0;j < i - j;++j) u += a[j] * a[i - j]; + for (j = i + 1;j < i + 32 - j;++j) u += 38 * a[j] * a[i + 32 - j]; + u *= 2; + if ((i & 1) == 0) { + u += a[i / 2] * a[i / 2]; + u += 38 * a[i / 2 + 16] * a[i / 2 + 16]; + } + out[i] = u; + } + squeeze(out); +} + +static inline void select(unsigned int p[64],unsigned int q[64],const unsigned int r[64],const unsigned int s[64],unsigned int b) +{ + unsigned int j; + unsigned int t; + unsigned int bminus1; + + bminus1 = b - 1; + for (j = 0;j < 64;++j) { + t = bminus1 & (r[j] ^ s[j]); + p[j] = s[j] ^ t; + q[j] = r[j] ^ t; + } +} + +static void mainloop(unsigned int work[64],const unsigned char e[32]) +{ + unsigned int xzm1[64]; + unsigned int xzm[64]; + unsigned int xzmb[64]; + unsigned int xzm1b[64]; + unsigned int xznb[64]; + unsigned int xzn1b[64]; + unsigned int a0[64]; + unsigned int a1[64]; + unsigned int b0[64]; + unsigned int b1[64]; + unsigned int c1[64]; + unsigned int r[32]; + unsigned int s[32]; + unsigned int t[32]; + unsigned int u[32]; + //unsigned int i; + unsigned int j; + unsigned int b; + int pos; + + for (j = 0;j < 32;++j) xzm1[j] = work[j]; + xzm1[32] = 1; + for (j = 33;j < 64;++j) xzm1[j] = 0; + + xzm[0] = 1; + for (j = 1;j < 64;++j) xzm[j] = 0; + + for (pos = 254;pos >= 0;--pos) { + b = e[pos / 8] >> (pos & 7); + b &= 1; + select(xzmb,xzm1b,xzm,xzm1,b); + add(a0,xzmb,xzmb + 32); + sub(a0 + 32,xzmb,xzmb + 32); + add(a1,xzm1b,xzm1b + 32); + sub(a1 + 32,xzm1b,xzm1b + 32); + square(b0,a0); + square(b0 + 32,a0 + 32); + mult(b1,a1,a0 + 32); + mult(b1 + 32,a1 + 32,a0); + add(c1,b1,b1 + 32); + sub(c1 + 32,b1,b1 + 32); + square(r,c1 + 32); + sub(s,b0,b0 + 32); + mult121665(t,s); + add(u,t,b0); + mult(xznb,b0,b0 + 32); + mult(xznb + 32,s,u); + square(xzn1b,c1); + mult(xzn1b + 32,r,work); + select(xzm,xzm1,xznb,xzn1b,b); + } + + for (j = 0;j < 64;++j) work[j] = xzm[j]; +} + +static void recip(unsigned int out[32],const unsigned int z[32]) +{ + unsigned int z2[32]; + unsigned int z9[32]; + unsigned int z11[32]; + unsigned int z2_5_0[32]; + unsigned int z2_10_0[32]; + unsigned int z2_20_0[32]; + unsigned int z2_50_0[32]; + unsigned int z2_100_0[32]; + unsigned int t0[32]; + unsigned int t1[32]; + int i; + + /* 2 */ square(z2,z); + /* 4 */ square(t1,z2); + /* 8 */ square(t0,t1); + /* 9 */ mult(z9,t0,z); + /* 11 */ mult(z11,z9,z2); + /* 22 */ square(t0,z11); + /* 2^5 - 2^0 = 31 */ mult(z2_5_0,t0,z9); + + /* 2^6 - 2^1 */ square(t0,z2_5_0); + /* 2^7 - 2^2 */ square(t1,t0); + /* 2^8 - 2^3 */ square(t0,t1); + /* 2^9 - 2^4 */ square(t1,t0); + /* 2^10 - 2^5 */ square(t0,t1); + /* 2^10 - 2^0 */ mult(z2_10_0,t0,z2_5_0); + + /* 2^11 - 2^1 */ square(t0,z2_10_0); + /* 2^12 - 2^2 */ square(t1,t0); + /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^20 - 2^0 */ mult(z2_20_0,t1,z2_10_0); + + /* 2^21 - 2^1 */ square(t0,z2_20_0); + /* 2^22 - 2^2 */ square(t1,t0); + /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^40 - 2^0 */ mult(t0,t1,z2_20_0); + + /* 2^41 - 2^1 */ square(t1,t0); + /* 2^42 - 2^2 */ square(t0,t1); + /* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { square(t1,t0); square(t0,t1); } + /* 2^50 - 2^0 */ mult(z2_50_0,t0,z2_10_0); + + /* 2^51 - 2^1 */ square(t0,z2_50_0); + /* 2^52 - 2^2 */ square(t1,t0); + /* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^100 - 2^0 */ mult(z2_100_0,t1,z2_50_0); + + /* 2^101 - 2^1 */ square(t1,z2_100_0); + /* 2^102 - 2^2 */ square(t0,t1); + /* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { square(t1,t0); square(t0,t1); } + /* 2^200 - 2^0 */ mult(t1,t0,z2_100_0); + + /* 2^201 - 2^1 */ square(t0,t1); + /* 2^202 - 2^2 */ square(t1,t0); + /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^250 - 2^0 */ mult(t0,t1,z2_50_0); + + /* 2^251 - 2^1 */ square(t1,t0); + /* 2^252 - 2^2 */ square(t0,t1); + /* 2^253 - 2^3 */ square(t1,t0); + /* 2^254 - 2^4 */ square(t0,t1); + /* 2^255 - 2^5 */ square(t1,t0); + /* 2^255 - 21 */ mult(out,t1,z11); +} + +static inline int crypto_scalarmult(unsigned char *q, + const unsigned char *n, + const unsigned char *p) +{ + unsigned int work[96]; + unsigned char e[32]; + unsigned int i; + for (i = 0;i < 32;++i) e[i] = n[i]; + e[0] &= 248; + e[31] &= 127; + e[31] |= 64; + for (i = 0;i < 32;++i) work[i] = p[i]; + mainloop(work,e); + recip(work + 32,work + 32); + mult(work + 64,work,work + 32); + freeze(work + 64); + for (i = 0;i < 32;++i) q[i] = work[64 + i]; + return 0; +} + +static const unsigned char base[32] = {9}; + +static inline int crypto_scalarmult_base(unsigned char *q, + const unsigned char *n) +{ + return crypto_scalarmult(q,n,base); +} + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +// This is the Ed25519 stuff from SUPERCOP: +// http://bench.cr.yp.to/supercop.html + +// Also public domain, newer version than the Ed25519 found in NaCl + +typedef struct +{ + crypto_uint32 v[32]; +} +fe25519; + +static void fe25519_sub(fe25519 *r, const fe25519 *x, const fe25519 *y); + +static inline crypto_uint32 equal(crypto_uint32 a,crypto_uint32 b) /* 16-bit inputs */ +{ + crypto_uint32 x = a ^ b; /* 0: yes; 1..65535: no */ + x -= 1; /* 4294967295: yes; 0..65534: no */ + x >>= 31; /* 1: yes; 0: no */ + return x; +} + +static inline crypto_uint32 ge(crypto_uint32 a,crypto_uint32 b) /* 16-bit inputs */ +{ + unsigned int x = a; + x -= (unsigned int) b; /* 0..65535: yes; 4294901761..4294967295: no */ + x >>= 31; /* 0: yes; 1: no */ + x ^= 1; /* 1: yes; 0: no */ + return x; +} + +static inline crypto_uint32 times19(crypto_uint32 a) +{ + return (a << 4) + (a << 1) + a; +} + +static inline crypto_uint32 times38(crypto_uint32 a) +{ + return (a << 5) + (a << 2) + (a << 1); +} + +static inline void reduce_add_sub(fe25519 *r) +{ + crypto_uint32 t; + int i,rep; + + for(rep=0;rep<4;rep++) + { + t = r->v[31] >> 7; + r->v[31] &= 127; + t = times19(t); + r->v[0] += t; + for(i=0;i<31;i++) + { + t = r->v[i] >> 8; + r->v[i+1] += t; + r->v[i] &= 255; + } + } +} + +static inline void reduce_mul(fe25519 *r) +{ + crypto_uint32 t; + int i,rep; + + for(rep=0;rep<2;rep++) + { + t = r->v[31] >> 7; + r->v[31] &= 127; + t = times19(t); + r->v[0] += t; + for(i=0;i<31;i++) + { + t = r->v[i] >> 8; + r->v[i+1] += t; + r->v[i] &= 255; + } + } +} + +/* reduction modulo 2^255-19 */ +static inline void fe25519_freeze(fe25519 *r) +{ + int i; + crypto_uint32 m = equal(r->v[31],127); + for(i=30;i>0;i--) + m &= equal(r->v[i],255); + m &= ge(r->v[0],237); + + m = -m; + + r->v[31] -= m&127; + for(i=30;i>0;i--) + r->v[i] -= m&255; + r->v[0] -= m&237; +} + +static inline void fe25519_unpack(fe25519 *r, const unsigned char x[32]) +{ + int i; + for(i=0;i<32;i++) r->v[i] = x[i]; + r->v[31] &= 127; +} + +/* Assumes input x being reduced below 2^255 */ +static inline void fe25519_pack(unsigned char r[32], const fe25519 *x) +{ + int i; + fe25519 y = *x; + fe25519_freeze(&y); + for(i=0;i<32;i++) + r[i] = y.v[i]; +} + +#if 0 +static int fe25519_iszero(const fe25519 *x) +{ + int i; + int r; + fe25519 t = *x; + fe25519_freeze(&t); + r = equal(t.v[0],0); + for(i=1;i<32;i++) + r &= equal(t.v[i],0); + return r; +} +#endif + +static inline int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y) +{ + int i; + fe25519 t1 = *x; + fe25519 t2 = *y; + fe25519_freeze(&t1); + fe25519_freeze(&t2); + for(i=0;i<32;i++) + if(t1.v[i] != t2.v[i]) return 0; + return 1; +} + +static inline void fe25519_cmov(fe25519 *r, const fe25519 *x, unsigned char b) +{ + int i; + crypto_uint32 mask = b; + mask = -mask; + for(i=0;i<32;i++) r->v[i] ^= mask & (x->v[i] ^ r->v[i]); +} + +static inline unsigned char fe25519_getparity(const fe25519 *x) +{ + fe25519 t = *x; + fe25519_freeze(&t); + return t.v[0] & 1; +} + +static inline void fe25519_setone(fe25519 *r) +{ + int i; + r->v[0] = 1; + for(i=1;i<32;i++) r->v[i]=0; +} + +static inline void fe25519_setzero(fe25519 *r) +{ + int i; + for(i=0;i<32;i++) r->v[i]=0; +} + +static inline void fe25519_neg(fe25519 *r, const fe25519 *x) +{ + fe25519 t; + int i; + for(i=0;i<32;i++) t.v[i]=x->v[i]; + fe25519_setzero(r); + fe25519_sub(r, r, &t); +} + +static inline void fe25519_add(fe25519 *r, const fe25519 *x, const fe25519 *y) +{ + int i; + for(i=0;i<32;i++) r->v[i] = x->v[i] + y->v[i]; + reduce_add_sub(r); +} + +static inline void fe25519_sub(fe25519 *r, const fe25519 *x, const fe25519 *y) +{ + int i; + crypto_uint32 t[32]; + t[0] = x->v[0] + 0x1da; + t[31] = x->v[31] + 0xfe; + for(i=1;i<31;i++) t[i] = x->v[i] + 0x1fe; + for(i=0;i<32;i++) r->v[i] = t[i] - y->v[i]; + reduce_add_sub(r); +} + +static inline void fe25519_mul(fe25519 *r, const fe25519 *x, const fe25519 *y) +{ + int i,j; + crypto_uint32 t[63]; + for(i=0;i<63;i++)t[i] = 0; + + for(i=0;i<32;i++) + for(j=0;j<32;j++) + t[i+j] += x->v[i] * y->v[j]; + + for(i=32;i<63;i++) + r->v[i-32] = t[i-32] + times38(t[i]); + r->v[31] = t[31]; /* result now in r[0]...r[31] */ + + reduce_mul(r); +} + +static inline void fe25519_square(fe25519 *r, const fe25519 *x) +{ + fe25519_mul(r, x, x); +} + +static void fe25519_invert(fe25519 *r, const fe25519 *x) +{ + fe25519 z2; + fe25519 z9; + fe25519 z11; + fe25519 z2_5_0; + fe25519 z2_10_0; + fe25519 z2_20_0; + fe25519 z2_50_0; + fe25519 z2_100_0; + fe25519 t0; + fe25519 t1; + int i; + + /* 2 */ fe25519_square(&z2,x); + /* 4 */ fe25519_square(&t1,&z2); + /* 8 */ fe25519_square(&t0,&t1); + /* 9 */ fe25519_mul(&z9,&t0,x); + /* 11 */ fe25519_mul(&z11,&z9,&z2); + /* 22 */ fe25519_square(&t0,&z11); + /* 2^5 - 2^0 = 31 */ fe25519_mul(&z2_5_0,&t0,&z9); + + /* 2^6 - 2^1 */ fe25519_square(&t0,&z2_5_0); + /* 2^7 - 2^2 */ fe25519_square(&t1,&t0); + /* 2^8 - 2^3 */ fe25519_square(&t0,&t1); + /* 2^9 - 2^4 */ fe25519_square(&t1,&t0); + /* 2^10 - 2^5 */ fe25519_square(&t0,&t1); + /* 2^10 - 2^0 */ fe25519_mul(&z2_10_0,&t0,&z2_5_0); + + /* 2^11 - 2^1 */ fe25519_square(&t0,&z2_10_0); + /* 2^12 - 2^2 */ fe25519_square(&t1,&t0); + /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { fe25519_square(&t0,&t1); fe25519_square(&t1,&t0); } + /* 2^20 - 2^0 */ fe25519_mul(&z2_20_0,&t1,&z2_10_0); + + /* 2^21 - 2^1 */ fe25519_square(&t0,&z2_20_0); + /* 2^22 - 2^2 */ fe25519_square(&t1,&t0); + /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { fe25519_square(&t0,&t1); fe25519_square(&t1,&t0); } + /* 2^40 - 2^0 */ fe25519_mul(&t0,&t1,&z2_20_0); + + /* 2^41 - 2^1 */ fe25519_square(&t1,&t0); + /* 2^42 - 2^2 */ fe25519_square(&t0,&t1); + /* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { fe25519_square(&t1,&t0); fe25519_square(&t0,&t1); } + /* 2^50 - 2^0 */ fe25519_mul(&z2_50_0,&t0,&z2_10_0); + + /* 2^51 - 2^1 */ fe25519_square(&t0,&z2_50_0); + /* 2^52 - 2^2 */ fe25519_square(&t1,&t0); + /* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { fe25519_square(&t0,&t1); fe25519_square(&t1,&t0); } + /* 2^100 - 2^0 */ fe25519_mul(&z2_100_0,&t1,&z2_50_0); + + /* 2^101 - 2^1 */ fe25519_square(&t1,&z2_100_0); + /* 2^102 - 2^2 */ fe25519_square(&t0,&t1); + /* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { fe25519_square(&t1,&t0); fe25519_square(&t0,&t1); } + /* 2^200 - 2^0 */ fe25519_mul(&t1,&t0,&z2_100_0); + + /* 2^201 - 2^1 */ fe25519_square(&t0,&t1); + /* 2^202 - 2^2 */ fe25519_square(&t1,&t0); + /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { fe25519_square(&t0,&t1); fe25519_square(&t1,&t0); } + /* 2^250 - 2^0 */ fe25519_mul(&t0,&t1,&z2_50_0); + + /* 2^251 - 2^1 */ fe25519_square(&t1,&t0); + /* 2^252 - 2^2 */ fe25519_square(&t0,&t1); + /* 2^253 - 2^3 */ fe25519_square(&t1,&t0); + /* 2^254 - 2^4 */ fe25519_square(&t0,&t1); + /* 2^255 - 2^5 */ fe25519_square(&t1,&t0); + /* 2^255 - 21 */ fe25519_mul(r,&t1,&z11); +} + +static void fe25519_pow2523(fe25519 *r, const fe25519 *x) +{ + fe25519 z2; + fe25519 z9; + fe25519 z11; + fe25519 z2_5_0; + fe25519 z2_10_0; + fe25519 z2_20_0; + fe25519 z2_50_0; + fe25519 z2_100_0; + fe25519 t; + int i; + + /* 2 */ fe25519_square(&z2,x); + /* 4 */ fe25519_square(&t,&z2); + /* 8 */ fe25519_square(&t,&t); + /* 9 */ fe25519_mul(&z9,&t,x); + /* 11 */ fe25519_mul(&z11,&z9,&z2); + /* 22 */ fe25519_square(&t,&z11); + /* 2^5 - 2^0 = 31 */ fe25519_mul(&z2_5_0,&t,&z9); + + /* 2^6 - 2^1 */ fe25519_square(&t,&z2_5_0); + /* 2^10 - 2^5 */ for (i = 1;i < 5;i++) { fe25519_square(&t,&t); } + /* 2^10 - 2^0 */ fe25519_mul(&z2_10_0,&t,&z2_5_0); + + /* 2^11 - 2^1 */ fe25519_square(&t,&z2_10_0); + /* 2^20 - 2^10 */ for (i = 1;i < 10;i++) { fe25519_square(&t,&t); } + /* 2^20 - 2^0 */ fe25519_mul(&z2_20_0,&t,&z2_10_0); + + /* 2^21 - 2^1 */ fe25519_square(&t,&z2_20_0); + /* 2^40 - 2^20 */ for (i = 1;i < 20;i++) { fe25519_square(&t,&t); } + /* 2^40 - 2^0 */ fe25519_mul(&t,&t,&z2_20_0); + + /* 2^41 - 2^1 */ fe25519_square(&t,&t); + /* 2^50 - 2^10 */ for (i = 1;i < 10;i++) { fe25519_square(&t,&t); } + /* 2^50 - 2^0 */ fe25519_mul(&z2_50_0,&t,&z2_10_0); + + /* 2^51 - 2^1 */ fe25519_square(&t,&z2_50_0); + /* 2^100 - 2^50 */ for (i = 1;i < 50;i++) { fe25519_square(&t,&t); } + /* 2^100 - 2^0 */ fe25519_mul(&z2_100_0,&t,&z2_50_0); + + /* 2^101 - 2^1 */ fe25519_square(&t,&z2_100_0); + /* 2^200 - 2^100 */ for (i = 1;i < 100;i++) { fe25519_square(&t,&t); } + /* 2^200 - 2^0 */ fe25519_mul(&t,&t,&z2_100_0); + + /* 2^201 - 2^1 */ fe25519_square(&t,&t); + /* 2^250 - 2^50 */ for (i = 1;i < 50;i++) { fe25519_square(&t,&t); } + /* 2^250 - 2^0 */ fe25519_mul(&t,&t,&z2_50_0); + + /* 2^251 - 2^1 */ fe25519_square(&t,&t); + /* 2^252 - 2^2 */ fe25519_square(&t,&t); + /* 2^252 - 3 */ fe25519_mul(r,&t,x); +} + +typedef struct +{ + crypto_uint32 v[32]; +} +sc25519; + +typedef struct +{ + crypto_uint32 v[16]; +} +shortsc25519; + +static const crypto_uint32 m[32] = {0xED, 0xD3, 0xF5, 0x5C, 0x1A, 0x63, 0x12, 0x58, 0xD6, 0x9C, 0xF7, 0xA2, 0xDE, 0xF9, 0xDE, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10}; + +static const crypto_uint32 mu[33] = {0x1B, 0x13, 0x2C, 0x0A, 0xA3, 0xE5, 0x9C, 0xED, 0xA7, 0x29, 0x63, 0x08, 0x5D, 0x21, 0x06, 0x21, + 0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F}; + +static inline crypto_uint32 lt(crypto_uint32 a,crypto_uint32 b) /* 16-bit inputs */ +{ + unsigned int x = a; + x -= (unsigned int) b; /* 0..65535: no; 4294901761..4294967295: yes */ + x >>= 31; /* 0: no; 1: yes */ + return x; +} + +/* Reduce coefficients of r before calling reduce_add_sub */ +static inline void reduce_add_sub(sc25519 *r) +{ + crypto_uint32 pb = 0; + crypto_uint32 b; + crypto_uint32 mask; + int i; + unsigned char t[32]; + + for(i=0;i<32;i++) + { + pb += m[i]; + b = lt(r->v[i],pb); + t[i] = r->v[i]-pb+(b<<8); + pb = b; + } + mask = b - 1; + for(i=0;i<32;i++) + r->v[i] ^= mask & (r->v[i] ^ t[i]); +} + +/* Reduce coefficients of x before calling barrett_reduce */ +static inline void barrett_reduce(sc25519 *r, const crypto_uint32 x[64]) +{ + /* See HAC, Alg. 14.42 */ + int i,j; + crypto_uint32 q2[66]; + crypto_uint32 *q3 = q2 + 33; + crypto_uint32 r1[33]; + crypto_uint32 r2[33]; + crypto_uint32 carry; + crypto_uint32 pb = 0; + crypto_uint32 b; + + for (i = 0;i < 66;++i) q2[i] = 0; + for (i = 0;i < 33;++i) r2[i] = 0; + + for(i=0;i<33;i++) + for(j=0;j<33;j++) + if(i+j >= 31) q2[i+j] += mu[i]*x[j+31]; + carry = q2[31] >> 8; + q2[32] += carry; + carry = q2[32] >> 8; + q2[33] += carry; + + for(i=0;i<33;i++)r1[i] = x[i]; + for(i=0;i<32;i++) + for(j=0;j<33;j++) + if(i+j < 33) r2[i+j] += m[i]*q3[j]; + + for(i=0;i<32;i++) + { + carry = r2[i] >> 8; + r2[i+1] += carry; + r2[i] &= 0xff; + } + + for(i=0;i<32;i++) + { + pb += r2[i]; + b = lt(r1[i],pb); + r->v[i] = r1[i]-pb+(b<<8); + pb = b; + } + + /* XXX: Can it really happen that r<0?, See HAC, Alg 14.42, Step 3 + * If so: Handle it here! + */ + + reduce_add_sub(r); + reduce_add_sub(r); +} + +static inline void sc25519_from32bytes(sc25519 *r, const unsigned char x[32]) +{ + int i; + crypto_uint32 t[64]; + for(i=0;i<32;i++) t[i] = x[i]; + for(i=32;i<64;++i) t[i] = 0; + barrett_reduce(r, t); +} + +#if 0 +static void shortsc25519_from16bytes(shortsc25519 *r, const unsigned char x[16]) +{ + int i; + for(i=0;i<16;i++) r->v[i] = x[i]; +} +#endif + +static inline void sc25519_from64bytes(sc25519 *r, const unsigned char x[64]) +{ + int i; + crypto_uint32 t[64]; + for(i=0;i<64;i++) t[i] = x[i]; + barrett_reduce(r, t); +} + +#if 0 +static void sc25519_from_shortsc(sc25519 *r, const shortsc25519 *x) +{ + int i; + for(i=0;i<16;i++) + r->v[i] = x->v[i]; + for(i=0;i<16;i++) + r->v[16+i] = 0; +} +#endif + +static inline void sc25519_to32bytes(unsigned char r[32], const sc25519 *x) +{ + int i; + for(i=0;i<32;i++) r[i] = x->v[i]; +} + +#if 0 +static int sc25519_iszero_vartime(const sc25519 *x) +{ + int i; + for(i=0;i<32;i++) + if(x->v[i] != 0) return 0; + return 1; +} +#endif + +#if 0 +static int sc25519_isshort_vartime(const sc25519 *x) +{ + int i; + for(i=31;i>15;i--) + if(x->v[i] != 0) return 0; + return 1; +} +#endif + +#if 0 +static int sc25519_lt_vartime(const sc25519 *x, const sc25519 *y) +{ + int i; + for(i=31;i>=0;i--) + { + if(x->v[i] < y->v[i]) return 1; + if(x->v[i] > y->v[i]) return 0; + } + return 0; +} +#endif + +static inline void sc25519_add(sc25519 *r, const sc25519 *x, const sc25519 *y) +{ + int i, carry; + for(i=0;i<32;i++) r->v[i] = x->v[i] + y->v[i]; + for(i=0;i<31;i++) + { + carry = r->v[i] >> 8; + r->v[i+1] += carry; + r->v[i] &= 0xff; + } + reduce_add_sub(r); +} + +#if 0 +static void sc25519_sub_nored(sc25519 *r, const sc25519 *x, const sc25519 *y) +{ + crypto_uint32 b = 0; + crypto_uint32 t; + int i; + for(i=0;i<32;i++) + { + t = x->v[i] - y->v[i] - b; + r->v[i] = t & 255; + b = (t >> 8) & 1; + } +} +#endif + +static inline void sc25519_mul(sc25519 *r, const sc25519 *x, const sc25519 *y) +{ + int i,j,carry; + crypto_uint32 t[64]; + for(i=0;i<64;i++)t[i] = 0; + + for(i=0;i<32;i++) + for(j=0;j<32;j++) + t[i+j] += x->v[i] * y->v[j]; + + /* Reduce coefficients */ + for(i=0;i<63;i++) + { + carry = t[i] >> 8; + t[i+1] += carry; + t[i] &= 0xff; + } + + barrett_reduce(r, t); +} + +#if 0 +static void sc25519_mul_shortsc(sc25519 *r, const sc25519 *x, const shortsc25519 *y) +{ + sc25519 t; + sc25519_from_shortsc(&t, y); + sc25519_mul(r, x, &t); +} +#endif + +static inline void sc25519_window3(signed char r[85], const sc25519 *s) +{ + char carry; + int i; + for(i=0;i<10;i++) + { + r[8*i+0] = s->v[3*i+0] & 7; + r[8*i+1] = (s->v[3*i+0] >> 3) & 7; + r[8*i+2] = (s->v[3*i+0] >> 6) & 7; + r[8*i+2] ^= (s->v[3*i+1] << 2) & 7; + r[8*i+3] = (s->v[3*i+1] >> 1) & 7; + r[8*i+4] = (s->v[3*i+1] >> 4) & 7; + r[8*i+5] = (s->v[3*i+1] >> 7) & 7; + r[8*i+5] ^= (s->v[3*i+2] << 1) & 7; + r[8*i+6] = (s->v[3*i+2] >> 2) & 7; + r[8*i+7] = (s->v[3*i+2] >> 5) & 7; + } + r[8*i+0] = s->v[3*i+0] & 7; + r[8*i+1] = (s->v[3*i+0] >> 3) & 7; + r[8*i+2] = (s->v[3*i+0] >> 6) & 7; + r[8*i+2] ^= (s->v[3*i+1] << 2) & 7; + r[8*i+3] = (s->v[3*i+1] >> 1) & 7; + r[8*i+4] = (s->v[3*i+1] >> 4) & 7; + + /* Making it signed */ + carry = 0; + for(i=0;i<84;i++) + { + r[i] += carry; + r[i+1] += r[i] >> 3; + r[i] &= 7; + carry = r[i] >> 2; + r[i] -= carry<<3; + } + r[84] += carry; +} + +#if 0 +static void sc25519_window5(signed char r[51], const sc25519 *s) +{ + char carry; + int i; + for(i=0;i<6;i++) + { + r[8*i+0] = s->v[5*i+0] & 31; + r[8*i+1] = (s->v[5*i+0] >> 5) & 31; + r[8*i+1] ^= (s->v[5*i+1] << 3) & 31; + r[8*i+2] = (s->v[5*i+1] >> 2) & 31; + r[8*i+3] = (s->v[5*i+1] >> 7) & 31; + r[8*i+3] ^= (s->v[5*i+2] << 1) & 31; + r[8*i+4] = (s->v[5*i+2] >> 4) & 31; + r[8*i+4] ^= (s->v[5*i+3] << 4) & 31; + r[8*i+5] = (s->v[5*i+3] >> 1) & 31; + r[8*i+6] = (s->v[5*i+3] >> 6) & 31; + r[8*i+6] ^= (s->v[5*i+4] << 2) & 31; + r[8*i+7] = (s->v[5*i+4] >> 3) & 31; + } + r[8*i+0] = s->v[5*i+0] & 31; + r[8*i+1] = (s->v[5*i+0] >> 5) & 31; + r[8*i+1] ^= (s->v[5*i+1] << 3) & 31; + r[8*i+2] = (s->v[5*i+1] >> 2) & 31; + + /* Making it signed */ + carry = 0; + for(i=0;i<50;i++) + { + r[i] += carry; + r[i+1] += r[i] >> 5; + r[i] &= 31; + carry = r[i] >> 4; + r[i] -= carry<<5; + } + r[50] += carry; +} +#endif + +static inline void sc25519_2interleave2(unsigned char r[127], const sc25519 *s1, const sc25519 *s2) +{ + int i; + for(i=0;i<31;i++) + { + r[4*i] = ( s1->v[i] & 3) ^ (( s2->v[i] & 3) << 2); + r[4*i+1] = ((s1->v[i] >> 2) & 3) ^ (((s2->v[i] >> 2) & 3) << 2); + r[4*i+2] = ((s1->v[i] >> 4) & 3) ^ (((s2->v[i] >> 4) & 3) << 2); + r[4*i+3] = ((s1->v[i] >> 6) & 3) ^ (((s2->v[i] >> 6) & 3) << 2); + } + r[124] = ( s1->v[31] & 3) ^ (( s2->v[31] & 3) << 2); + r[125] = ((s1->v[31] >> 2) & 3) ^ (((s2->v[31] >> 2) & 3) << 2); + r[126] = ((s1->v[31] >> 4) & 3) ^ (((s2->v[31] >> 4) & 3) << 2); +} + +typedef struct +{ + fe25519 x; + fe25519 y; + fe25519 z; + fe25519 t; +} ge25519; + +/* d */ +static const fe25519 ge25519_ecd = {{0xA3, 0x78, 0x59, 0x13, 0xCA, 0x4D, 0xEB, 0x75, 0xAB, 0xD8, 0x41, 0x41, 0x4D, 0x0A, 0x70, 0x00, + 0x98, 0xE8, 0x79, 0x77, 0x79, 0x40, 0xC7, 0x8C, 0x73, 0xFE, 0x6F, 0x2B, 0xEE, 0x6C, 0x03, 0x52}}; +/* 2*d */ +static const fe25519 ge25519_ec2d = {{0x59, 0xF1, 0xB2, 0x26, 0x94, 0x9B, 0xD6, 0xEB, 0x56, 0xB1, 0x83, 0x82, 0x9A, 0x14, 0xE0, 0x00, + 0x30, 0xD1, 0xF3, 0xEE, 0xF2, 0x80, 0x8E, 0x19, 0xE7, 0xFC, 0xDF, 0x56, 0xDC, 0xD9, 0x06, 0x24}}; +/* sqrt(-1) */ +static const fe25519 ge25519_sqrtm1 = {{0xB0, 0xA0, 0x0E, 0x4A, 0x27, 0x1B, 0xEE, 0xC4, 0x78, 0xE4, 0x2F, 0xAD, 0x06, 0x18, 0x43, 0x2F, + 0xA7, 0xD7, 0xFB, 0x3D, 0x99, 0x00, 0x4D, 0x2B, 0x0B, 0xDF, 0xC1, 0x4F, 0x80, 0x24, 0x83, 0x2B}}; + +#define ge25519_p3 ge25519 + +typedef struct +{ + fe25519 x; + fe25519 z; + fe25519 y; + fe25519 t; +} ge25519_p1p1; + +typedef struct +{ + fe25519 x; + fe25519 y; + fe25519 z; +} ge25519_p2; + +typedef struct +{ + fe25519 x; + fe25519 y; +} ge25519_aff; + + +/* Packed coordinates of the base point */ +static const ge25519 ge25519_base = {{{0x1A, 0xD5, 0x25, 0x8F, 0x60, 0x2D, 0x56, 0xC9, 0xB2, 0xA7, 0x25, 0x95, 0x60, 0xC7, 0x2C, 0x69, + 0x5C, 0xDC, 0xD6, 0xFD, 0x31, 0xE2, 0xA4, 0xC0, 0xFE, 0x53, 0x6E, 0xCD, 0xD3, 0x36, 0x69, 0x21}}, + {{0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0xA3, 0xDD, 0xB7, 0xA5, 0xB3, 0x8A, 0xDE, 0x6D, 0xF5, 0x52, 0x51, 0x77, 0x80, 0x9F, 0xF0, 0x20, + 0x7D, 0xE3, 0xAB, 0x64, 0x8E, 0x4E, 0xEA, 0x66, 0x65, 0x76, 0x8B, 0xD7, 0x0F, 0x5F, 0x87, 0x67}}}; + +/* Multiples of the base point in affine representation */ +static const ge25519_aff ge25519_base_multiples_affine[425] = { +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x1a, 0xd5, 0x25, 0x8f, 0x60, 0x2d, 0x56, 0xc9, 0xb2, 0xa7, 0x25, 0x95, 0x60, 0xc7, 0x2c, 0x69, 0x5c, 0xdc, 0xd6, 0xfd, 0x31, 0xe2, 0xa4, 0xc0, 0xfe, 0x53, 0x6e, 0xcd, 0xd3, 0x36, 0x69, 0x21}} , + {{0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}}}, +{{{0x0e, 0xce, 0x43, 0x28, 0x4e, 0xa1, 0xc5, 0x83, 0x5f, 0xa4, 0xd7, 0x15, 0x45, 0x8e, 0x0d, 0x08, 0xac, 0xe7, 0x33, 0x18, 0x7d, 0x3b, 0x04, 0x3d, 0x6c, 0x04, 0x5a, 0x9f, 0x4c, 0x38, 0xab, 0x36}} , + {{0xc9, 0xa3, 0xf8, 0x6a, 0xae, 0x46, 0x5f, 0x0e, 0x56, 0x51, 0x38, 0x64, 0x51, 0x0f, 0x39, 0x97, 0x56, 0x1f, 0xa2, 0xc9, 0xe8, 0x5e, 0xa2, 0x1d, 0xc2, 0x29, 0x23, 0x09, 0xf3, 0xcd, 0x60, 0x22}}}, +{{{0x5c, 0xe2, 0xf8, 0xd3, 0x5f, 0x48, 0x62, 0xac, 0x86, 0x48, 0x62, 0x81, 0x19, 0x98, 0x43, 0x63, 0x3a, 0xc8, 0xda, 0x3e, 0x74, 0xae, 0xf4, 0x1f, 0x49, 0x8f, 0x92, 0x22, 0x4a, 0x9c, 0xae, 0x67}} , + {{0xd4, 0xb4, 0xf5, 0x78, 0x48, 0x68, 0xc3, 0x02, 0x04, 0x03, 0x24, 0x67, 0x17, 0xec, 0x16, 0x9f, 0xf7, 0x9e, 0x26, 0x60, 0x8e, 0xa1, 0x26, 0xa1, 0xab, 0x69, 0xee, 0x77, 0xd1, 0xb1, 0x67, 0x12}}}, +{{{0x70, 0xf8, 0xc9, 0xc4, 0x57, 0xa6, 0x3a, 0x49, 0x47, 0x15, 0xce, 0x93, 0xc1, 0x9e, 0x73, 0x1a, 0xf9, 0x20, 0x35, 0x7a, 0xb8, 0xd4, 0x25, 0x83, 0x46, 0xf1, 0xcf, 0x56, 0xdb, 0xa8, 0x3d, 0x20}} , + {{0x2f, 0x11, 0x32, 0xca, 0x61, 0xab, 0x38, 0xdf, 0xf0, 0x0f, 0x2f, 0xea, 0x32, 0x28, 0xf2, 0x4c, 0x6c, 0x71, 0xd5, 0x80, 0x85, 0xb8, 0x0e, 0x47, 0xe1, 0x95, 0x15, 0xcb, 0x27, 0xe8, 0xd0, 0x47}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xc8, 0x84, 0xa5, 0x08, 0xbc, 0xfd, 0x87, 0x3b, 0x99, 0x8b, 0x69, 0x80, 0x7b, 0xc6, 0x3a, 0xeb, 0x93, 0xcf, 0x4e, 0xf8, 0x5c, 0x2d, 0x86, 0x42, 0xb6, 0x71, 0xd7, 0x97, 0x5f, 0xe1, 0x42, 0x67}} , + {{0xb4, 0xb9, 0x37, 0xfc, 0xa9, 0x5b, 0x2f, 0x1e, 0x93, 0xe4, 0x1e, 0x62, 0xfc, 0x3c, 0x78, 0x81, 0x8f, 0xf3, 0x8a, 0x66, 0x09, 0x6f, 0xad, 0x6e, 0x79, 0x73, 0xe5, 0xc9, 0x00, 0x06, 0xd3, 0x21}}}, +{{{0xf8, 0xf9, 0x28, 0x6c, 0x6d, 0x59, 0xb2, 0x59, 0x74, 0x23, 0xbf, 0xe7, 0x33, 0x8d, 0x57, 0x09, 0x91, 0x9c, 0x24, 0x08, 0x15, 0x2b, 0xe2, 0xb8, 0xee, 0x3a, 0xe5, 0x27, 0x06, 0x86, 0xa4, 0x23}} , + {{0xeb, 0x27, 0x67, 0xc1, 0x37, 0xab, 0x7a, 0xd8, 0x27, 0x9c, 0x07, 0x8e, 0xff, 0x11, 0x6a, 0xb0, 0x78, 0x6e, 0xad, 0x3a, 0x2e, 0x0f, 0x98, 0x9f, 0x72, 0xc3, 0x7f, 0x82, 0xf2, 0x96, 0x96, 0x70}}}, +{{{0x81, 0x6b, 0x88, 0xe8, 0x1e, 0xc7, 0x77, 0x96, 0x0e, 0xa1, 0xa9, 0x52, 0xe0, 0xd8, 0x0e, 0x61, 0x9e, 0x79, 0x2d, 0x95, 0x9c, 0x8d, 0x96, 0xe0, 0x06, 0x40, 0x5d, 0x87, 0x28, 0x5f, 0x98, 0x70}} , + {{0xf1, 0x79, 0x7b, 0xed, 0x4f, 0x44, 0xb2, 0xe7, 0x08, 0x0d, 0xc2, 0x08, 0x12, 0xd2, 0x9f, 0xdf, 0xcd, 0x93, 0x20, 0x8a, 0xcf, 0x33, 0xca, 0x6d, 0x89, 0xb9, 0x77, 0xc8, 0x93, 0x1b, 0x4e, 0x60}}}, +{{{0x26, 0x4f, 0x7e, 0x97, 0xf6, 0x40, 0xdd, 0x4f, 0xfc, 0x52, 0x78, 0xf9, 0x90, 0x31, 0x03, 0xe6, 0x7d, 0x56, 0x39, 0x0b, 0x1d, 0x56, 0x82, 0x85, 0xf9, 0x1a, 0x42, 0x17, 0x69, 0x6c, 0xcf, 0x39}} , + {{0x69, 0xd2, 0x06, 0x3a, 0x4f, 0x39, 0x2d, 0xf9, 0x38, 0x40, 0x8c, 0x4c, 0xe7, 0x05, 0x12, 0xb4, 0x78, 0x8b, 0xf8, 0xc0, 0xec, 0x93, 0xde, 0x7a, 0x6b, 0xce, 0x2c, 0xe1, 0x0e, 0xa9, 0x34, 0x44}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x0b, 0xa4, 0x3c, 0xb0, 0x0f, 0x7a, 0x51, 0xf1, 0x78, 0xd6, 0xd9, 0x6a, 0xfd, 0x46, 0xe8, 0xb8, 0xa8, 0x79, 0x1d, 0x87, 0xf9, 0x90, 0xf2, 0x9c, 0x13, 0x29, 0xf8, 0x0b, 0x20, 0x64, 0xfa, 0x05}} , + {{0x26, 0x09, 0xda, 0x17, 0xaf, 0x95, 0xd6, 0xfb, 0x6a, 0x19, 0x0d, 0x6e, 0x5e, 0x12, 0xf1, 0x99, 0x4c, 0xaa, 0xa8, 0x6f, 0x79, 0x86, 0xf4, 0x72, 0x28, 0x00, 0x26, 0xf9, 0xea, 0x9e, 0x19, 0x3d}}}, +{{{0x87, 0xdd, 0xcf, 0xf0, 0x5b, 0x49, 0xa2, 0x5d, 0x40, 0x7a, 0x23, 0x26, 0xa4, 0x7a, 0x83, 0x8a, 0xb7, 0x8b, 0xd2, 0x1a, 0xbf, 0xea, 0x02, 0x24, 0x08, 0x5f, 0x7b, 0xa9, 0xb1, 0xbe, 0x9d, 0x37}} , + {{0xfc, 0x86, 0x4b, 0x08, 0xee, 0xe7, 0xa0, 0xfd, 0x21, 0x45, 0x09, 0x34, 0xc1, 0x61, 0x32, 0x23, 0xfc, 0x9b, 0x55, 0x48, 0x53, 0x99, 0xf7, 0x63, 0xd0, 0x99, 0xce, 0x01, 0xe0, 0x9f, 0xeb, 0x28}}}, +{{{0x47, 0xfc, 0xab, 0x5a, 0x17, 0xf0, 0x85, 0x56, 0x3a, 0x30, 0x86, 0x20, 0x28, 0x4b, 0x8e, 0x44, 0x74, 0x3a, 0x6e, 0x02, 0xf1, 0x32, 0x8f, 0x9f, 0x3f, 0x08, 0x35, 0xe9, 0xca, 0x16, 0x5f, 0x6e}} , + {{0x1c, 0x59, 0x1c, 0x65, 0x5d, 0x34, 0xa4, 0x09, 0xcd, 0x13, 0x9c, 0x70, 0x7d, 0xb1, 0x2a, 0xc5, 0x88, 0xaf, 0x0b, 0x60, 0xc7, 0x9f, 0x34, 0x8d, 0xd6, 0xb7, 0x7f, 0xea, 0x78, 0x65, 0x8d, 0x77}}}, +{{{0x56, 0xa5, 0xc2, 0x0c, 0xdd, 0xbc, 0xb8, 0x20, 0x6d, 0x57, 0x61, 0xb5, 0xfb, 0x78, 0xb5, 0xd4, 0x49, 0x54, 0x90, 0x26, 0xc1, 0xcb, 0xe9, 0xe6, 0xbf, 0xec, 0x1d, 0x4e, 0xed, 0x07, 0x7e, 0x5e}} , + {{0xc7, 0xf6, 0x6c, 0x56, 0x31, 0x20, 0x14, 0x0e, 0xa8, 0xd9, 0x27, 0xc1, 0x9a, 0x3d, 0x1b, 0x7d, 0x0e, 0x26, 0xd3, 0x81, 0xaa, 0xeb, 0xf5, 0x6b, 0x79, 0x02, 0xf1, 0x51, 0x5c, 0x75, 0x55, 0x0f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x0a, 0x34, 0xcd, 0x82, 0x3c, 0x33, 0x09, 0x54, 0xd2, 0x61, 0x39, 0x30, 0x9b, 0xfd, 0xef, 0x21, 0x26, 0xd4, 0x70, 0xfa, 0xee, 0xf9, 0x31, 0x33, 0x73, 0x84, 0xd0, 0xb3, 0x81, 0xbf, 0xec, 0x2e}} , + {{0xe8, 0x93, 0x8b, 0x00, 0x64, 0xf7, 0x9c, 0xb8, 0x74, 0xe0, 0xe6, 0x49, 0x48, 0x4d, 0x4d, 0x48, 0xb6, 0x19, 0xa1, 0x40, 0xb7, 0xd9, 0x32, 0x41, 0x7c, 0x82, 0x37, 0xa1, 0x2d, 0xdc, 0xd2, 0x54}}}, +{{{0x68, 0x2b, 0x4a, 0x5b, 0xd5, 0xc7, 0x51, 0x91, 0x1d, 0xe1, 0x2a, 0x4b, 0xc4, 0x47, 0xf1, 0xbc, 0x7a, 0xb3, 0xcb, 0xc8, 0xb6, 0x7c, 0xac, 0x90, 0x05, 0xfd, 0xf3, 0xf9, 0x52, 0x3a, 0x11, 0x6b}} , + {{0x3d, 0xc1, 0x27, 0xf3, 0x59, 0x43, 0x95, 0x90, 0xc5, 0x96, 0x79, 0xf5, 0xf4, 0x95, 0x65, 0x29, 0x06, 0x9c, 0x51, 0x05, 0x18, 0xda, 0xb8, 0x2e, 0x79, 0x7e, 0x69, 0x59, 0x71, 0x01, 0xeb, 0x1a}}}, +{{{0x15, 0x06, 0x49, 0xb6, 0x8a, 0x3c, 0xea, 0x2f, 0x34, 0x20, 0x14, 0xc3, 0xaa, 0xd6, 0xaf, 0x2c, 0x3e, 0xbd, 0x65, 0x20, 0xe2, 0x4d, 0x4b, 0x3b, 0xeb, 0x9f, 0x4a, 0xc3, 0xad, 0xa4, 0x3b, 0x60}} , + {{0xbc, 0x58, 0xe6, 0xc0, 0x95, 0x2a, 0x2a, 0x81, 0x9a, 0x7a, 0xf3, 0xd2, 0x06, 0xbe, 0x48, 0xbc, 0x0c, 0xc5, 0x46, 0xe0, 0x6a, 0xd4, 0xac, 0x0f, 0xd9, 0xcc, 0x82, 0x34, 0x2c, 0xaf, 0xdb, 0x1f}}}, +{{{0xf7, 0x17, 0x13, 0xbd, 0xfb, 0xbc, 0xd2, 0xec, 0x45, 0xb3, 0x15, 0x31, 0xe9, 0xaf, 0x82, 0x84, 0x3d, 0x28, 0xc6, 0xfc, 0x11, 0xf5, 0x41, 0xb5, 0x8b, 0xd3, 0x12, 0x76, 0x52, 0xe7, 0x1a, 0x3c}} , + {{0x4e, 0x36, 0x11, 0x07, 0xa2, 0x15, 0x20, 0x51, 0xc4, 0x2a, 0xc3, 0x62, 0x8b, 0x5e, 0x7f, 0xa6, 0x0f, 0xf9, 0x45, 0x85, 0x6c, 0x11, 0x86, 0xb7, 0x7e, 0xe5, 0xd7, 0xf9, 0xc3, 0x91, 0x1c, 0x05}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xea, 0xd6, 0xde, 0x29, 0x3a, 0x00, 0xb9, 0x02, 0x59, 0xcb, 0x26, 0xc4, 0xba, 0x99, 0xb1, 0x97, 0x2f, 0x8e, 0x00, 0x92, 0x26, 0x4f, 0x52, 0xeb, 0x47, 0x1b, 0x89, 0x8b, 0x24, 0xc0, 0x13, 0x7d}} , + {{0xd5, 0x20, 0x5b, 0x80, 0xa6, 0x80, 0x20, 0x95, 0xc3, 0xe9, 0x9f, 0x8e, 0x87, 0x9e, 0x1e, 0x9e, 0x7a, 0xc7, 0xcc, 0x75, 0x6c, 0xa5, 0xf1, 0x91, 0x1a, 0xa8, 0x01, 0x2c, 0xab, 0x76, 0xa9, 0x59}}}, +{{{0xde, 0xc9, 0xb1, 0x31, 0x10, 0x16, 0xaa, 0x35, 0x14, 0x6a, 0xd4, 0xb5, 0x34, 0x82, 0x71, 0xd2, 0x4a, 0x5d, 0x9a, 0x1f, 0x53, 0x26, 0x3c, 0xe5, 0x8e, 0x8d, 0x33, 0x7f, 0xff, 0xa9, 0xd5, 0x17}} , + {{0x89, 0xaf, 0xf6, 0xa4, 0x64, 0xd5, 0x10, 0xe0, 0x1d, 0xad, 0xef, 0x44, 0xbd, 0xda, 0x83, 0xac, 0x7a, 0xa8, 0xf0, 0x1c, 0x07, 0xf9, 0xc3, 0x43, 0x6c, 0x3f, 0xb7, 0xd3, 0x87, 0x22, 0x02, 0x73}}}, +{{{0x64, 0x1d, 0x49, 0x13, 0x2f, 0x71, 0xec, 0x69, 0x87, 0xd0, 0x42, 0xee, 0x13, 0xec, 0xe3, 0xed, 0x56, 0x7b, 0xbf, 0xbd, 0x8c, 0x2f, 0x7d, 0x7b, 0x9d, 0x28, 0xec, 0x8e, 0x76, 0x2f, 0x6f, 0x08}} , + {{0x22, 0xf5, 0x5f, 0x4d, 0x15, 0xef, 0xfc, 0x4e, 0x57, 0x03, 0x36, 0x89, 0xf0, 0xeb, 0x5b, 0x91, 0xd6, 0xe2, 0xca, 0x01, 0xa5, 0xee, 0x52, 0xec, 0xa0, 0x3c, 0x8f, 0x33, 0x90, 0x5a, 0x94, 0x72}}}, +{{{0x8a, 0x4b, 0xe7, 0x38, 0xbc, 0xda, 0xc2, 0xb0, 0x85, 0xe1, 0x4a, 0xfe, 0x2d, 0x44, 0x84, 0xcb, 0x20, 0x6b, 0x2d, 0xbf, 0x11, 0x9c, 0xd7, 0xbe, 0xd3, 0x3e, 0x5f, 0xbf, 0x68, 0xbc, 0xa8, 0x07}} , + {{0x01, 0x89, 0x28, 0x22, 0x6a, 0x78, 0xaa, 0x29, 0x03, 0xc8, 0x74, 0x95, 0x03, 0x3e, 0xdc, 0xbd, 0x07, 0x13, 0xa8, 0xa2, 0x20, 0x2d, 0xb3, 0x18, 0x70, 0x42, 0xfd, 0x7a, 0xc4, 0xd7, 0x49, 0x72}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x02, 0xff, 0x32, 0x2b, 0x5c, 0x93, 0x54, 0x32, 0xe8, 0x57, 0x54, 0x1a, 0x8b, 0x33, 0x60, 0x65, 0xd3, 0x67, 0xa4, 0xc1, 0x26, 0xc4, 0xa4, 0x34, 0x1f, 0x9b, 0xa7, 0xa9, 0xf4, 0xd9, 0x4f, 0x5b}} , + {{0x46, 0x8d, 0xb0, 0x33, 0x54, 0x26, 0x5b, 0x68, 0xdf, 0xbb, 0xc5, 0xec, 0xc2, 0xf9, 0x3c, 0x5a, 0x37, 0xc1, 0x8e, 0x27, 0x47, 0xaa, 0x49, 0x5a, 0xf8, 0xfb, 0x68, 0x04, 0x23, 0xd1, 0xeb, 0x40}}}, +{{{0x65, 0xa5, 0x11, 0x84, 0x8a, 0x67, 0x9d, 0x9e, 0xd1, 0x44, 0x68, 0x7a, 0x34, 0xe1, 0x9f, 0xa3, 0x54, 0xcd, 0x07, 0xca, 0x79, 0x1f, 0x54, 0x2f, 0x13, 0x70, 0x4e, 0xee, 0xa2, 0xfa, 0xe7, 0x5d}} , + {{0x36, 0xec, 0x54, 0xf8, 0xce, 0xe4, 0x85, 0xdf, 0xf6, 0x6f, 0x1d, 0x90, 0x08, 0xbc, 0xe8, 0xc0, 0x92, 0x2d, 0x43, 0x6b, 0x92, 0xa9, 0x8e, 0xab, 0x0a, 0x2e, 0x1c, 0x1e, 0x64, 0x23, 0x9f, 0x2c}}}, +{{{0xa7, 0xd6, 0x2e, 0xd5, 0xcc, 0xd4, 0xcb, 0x5a, 0x3b, 0xa7, 0xf9, 0x46, 0x03, 0x1d, 0xad, 0x2b, 0x34, 0x31, 0x90, 0x00, 0x46, 0x08, 0x82, 0x14, 0xc4, 0xe0, 0x9c, 0xf0, 0xe3, 0x55, 0x43, 0x31}} , + {{0x60, 0xd6, 0xdd, 0x78, 0xe6, 0xd4, 0x22, 0x42, 0x1f, 0x00, 0xf9, 0xb1, 0x6a, 0x63, 0xe2, 0x92, 0x59, 0xd1, 0x1a, 0xb7, 0x00, 0x54, 0x29, 0xc9, 0xc1, 0xf6, 0x6f, 0x7a, 0xc5, 0x3c, 0x5f, 0x65}}}, +{{{0x27, 0x4f, 0xd0, 0x72, 0xb1, 0x11, 0x14, 0x27, 0x15, 0x94, 0x48, 0x81, 0x7e, 0x74, 0xd8, 0x32, 0xd5, 0xd1, 0x11, 0x28, 0x60, 0x63, 0x36, 0x32, 0x37, 0xb5, 0x13, 0x1c, 0xa0, 0x37, 0xe3, 0x74}} , + {{0xf1, 0x25, 0x4e, 0x11, 0x96, 0x67, 0xe6, 0x1c, 0xc2, 0xb2, 0x53, 0xe2, 0xda, 0x85, 0xee, 0xb2, 0x9f, 0x59, 0xf3, 0xba, 0xbd, 0xfa, 0xcf, 0x6e, 0xf9, 0xda, 0xa4, 0xb3, 0x02, 0x8f, 0x64, 0x08}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x34, 0x94, 0xf2, 0x64, 0x54, 0x47, 0x37, 0x07, 0x40, 0x8a, 0x20, 0xba, 0x4a, 0x55, 0xd7, 0x3f, 0x47, 0xba, 0x25, 0x23, 0x14, 0xb0, 0x2c, 0xe8, 0x55, 0xa8, 0xa6, 0xef, 0x51, 0xbd, 0x6f, 0x6a}} , + {{0x71, 0xd6, 0x16, 0x76, 0xb2, 0x06, 0xea, 0x79, 0xf5, 0xc4, 0xc3, 0x52, 0x7e, 0x61, 0xd1, 0xe1, 0xad, 0x70, 0x78, 0x1d, 0x16, 0x11, 0xf8, 0x7c, 0x2b, 0xfc, 0x55, 0x9f, 0x52, 0xf8, 0xf5, 0x16}}}, +{{{0x34, 0x96, 0x9a, 0xf6, 0xc5, 0xe0, 0x14, 0x03, 0x24, 0x0e, 0x4c, 0xad, 0x9e, 0x9a, 0x70, 0x23, 0x96, 0xb2, 0xf1, 0x2e, 0x9d, 0xc3, 0x32, 0x9b, 0x54, 0xa5, 0x73, 0xde, 0x88, 0xb1, 0x3e, 0x24}} , + {{0xf6, 0xe2, 0x4c, 0x1f, 0x5b, 0xb2, 0xaf, 0x82, 0xa5, 0xcf, 0x81, 0x10, 0x04, 0xef, 0xdb, 0xa2, 0xcc, 0x24, 0xb2, 0x7e, 0x0b, 0x7a, 0xeb, 0x01, 0xd8, 0x52, 0xf4, 0x51, 0x89, 0x29, 0x79, 0x37}}}, +{{{0x74, 0xde, 0x12, 0xf3, 0x68, 0xb7, 0x66, 0xc3, 0xee, 0x68, 0xdc, 0x81, 0xb5, 0x55, 0x99, 0xab, 0xd9, 0x28, 0x63, 0x6d, 0x8b, 0x40, 0x69, 0x75, 0x6c, 0xcd, 0x5c, 0x2a, 0x7e, 0x32, 0x7b, 0x29}} , + {{0x02, 0xcc, 0x22, 0x74, 0x4d, 0x19, 0x07, 0xc0, 0xda, 0xb5, 0x76, 0x51, 0x2a, 0xaa, 0xa6, 0x0a, 0x5f, 0x26, 0xd4, 0xbc, 0xaf, 0x48, 0x88, 0x7f, 0x02, 0xbc, 0xf2, 0xe1, 0xcf, 0xe9, 0xdd, 0x15}}}, +{{{0xed, 0xb5, 0x9a, 0x8c, 0x9a, 0xdd, 0x27, 0xf4, 0x7f, 0x47, 0xd9, 0x52, 0xa7, 0xcd, 0x65, 0xa5, 0x31, 0x22, 0xed, 0xa6, 0x63, 0x5b, 0x80, 0x4a, 0xad, 0x4d, 0xed, 0xbf, 0xee, 0x49, 0xb3, 0x06}} , + {{0xf8, 0x64, 0x8b, 0x60, 0x90, 0xe9, 0xde, 0x44, 0x77, 0xb9, 0x07, 0x36, 0x32, 0xc2, 0x50, 0xf5, 0x65, 0xdf, 0x48, 0x4c, 0x37, 0xaa, 0x68, 0xab, 0x9a, 0x1f, 0x3e, 0xff, 0x89, 0x92, 0xa0, 0x07}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x7d, 0x4f, 0x9c, 0x19, 0xc0, 0x4a, 0x31, 0xec, 0xf9, 0xaa, 0xeb, 0xb2, 0x16, 0x9c, 0xa3, 0x66, 0x5f, 0xd1, 0xd4, 0xed, 0xb8, 0x92, 0x1c, 0xab, 0xda, 0xea, 0xd9, 0x57, 0xdf, 0x4c, 0x2a, 0x48}} , + {{0x4b, 0xb0, 0x4e, 0x6e, 0x11, 0x3b, 0x51, 0xbd, 0x6a, 0xfd, 0xe4, 0x25, 0xa5, 0x5f, 0x11, 0x3f, 0x98, 0x92, 0x51, 0x14, 0xc6, 0x5f, 0x3c, 0x0b, 0xa8, 0xf7, 0xc2, 0x81, 0x43, 0xde, 0x91, 0x73}}}, +{{{0x3c, 0x8f, 0x9f, 0x33, 0x2a, 0x1f, 0x43, 0x33, 0x8f, 0x68, 0xff, 0x1f, 0x3d, 0x73, 0x6b, 0xbf, 0x68, 0xcc, 0x7d, 0x13, 0x6c, 0x24, 0x4b, 0xcc, 0x4d, 0x24, 0x0d, 0xfe, 0xde, 0x86, 0xad, 0x3b}} , + {{0x79, 0x51, 0x81, 0x01, 0xdc, 0x73, 0x53, 0xe0, 0x6e, 0x9b, 0xea, 0x68, 0x3f, 0x5c, 0x14, 0x84, 0x53, 0x8d, 0x4b, 0xc0, 0x9f, 0x9f, 0x89, 0x2b, 0x8c, 0xba, 0x86, 0xfa, 0xf2, 0xcd, 0xe3, 0x2d}}}, +{{{0x06, 0xf9, 0x29, 0x5a, 0xdb, 0x3d, 0x84, 0x52, 0xab, 0xcc, 0x6b, 0x60, 0x9d, 0xb7, 0x4a, 0x0e, 0x36, 0x63, 0x91, 0xad, 0xa0, 0x95, 0xb0, 0x97, 0x89, 0x4e, 0xcf, 0x7d, 0x3c, 0xe5, 0x7c, 0x28}} , + {{0x2e, 0x69, 0x98, 0xfd, 0xc6, 0xbd, 0xcc, 0xca, 0xdf, 0x9a, 0x44, 0x7e, 0x9d, 0xca, 0x89, 0x6d, 0xbf, 0x27, 0xc2, 0xf8, 0xcd, 0x46, 0x00, 0x2b, 0xb5, 0x58, 0x4e, 0xb7, 0x89, 0x09, 0xe9, 0x2d}}}, +{{{0x54, 0xbe, 0x75, 0xcb, 0x05, 0xb0, 0x54, 0xb7, 0xe7, 0x26, 0x86, 0x4a, 0xfc, 0x19, 0xcf, 0x27, 0x46, 0xd4, 0x22, 0x96, 0x5a, 0x11, 0xe8, 0xd5, 0x1b, 0xed, 0x71, 0xc5, 0x5d, 0xc8, 0xaf, 0x45}} , + {{0x40, 0x7b, 0x77, 0x57, 0x49, 0x9e, 0x80, 0x39, 0x23, 0xee, 0x81, 0x0b, 0x22, 0xcf, 0xdb, 0x7a, 0x2f, 0x14, 0xb8, 0x57, 0x8f, 0xa1, 0x39, 0x1e, 0x77, 0xfc, 0x0b, 0xa6, 0xbf, 0x8a, 0x0c, 0x6c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x77, 0x3a, 0xd4, 0xd8, 0x27, 0xcf, 0xe8, 0xa1, 0x72, 0x9d, 0xca, 0xdd, 0x0d, 0x96, 0xda, 0x79, 0xed, 0x56, 0x42, 0x15, 0x60, 0xc7, 0x1c, 0x6b, 0x26, 0x30, 0xf6, 0x6a, 0x95, 0x67, 0xf3, 0x0a}} , + {{0xc5, 0x08, 0xa4, 0x2b, 0x2f, 0xbd, 0x31, 0x81, 0x2a, 0xa6, 0xb6, 0xe4, 0x00, 0x91, 0xda, 0x3d, 0xb2, 0xb0, 0x96, 0xce, 0x8a, 0xd2, 0x8d, 0x70, 0xb3, 0xd3, 0x34, 0x01, 0x90, 0x8d, 0x10, 0x21}}}, +{{{0x33, 0x0d, 0xe7, 0xba, 0x4f, 0x07, 0xdf, 0x8d, 0xea, 0x7d, 0xa0, 0xc5, 0xd6, 0xb1, 0xb0, 0xe5, 0x57, 0x1b, 0x5b, 0xf5, 0x45, 0x13, 0x14, 0x64, 0x5a, 0xeb, 0x5c, 0xfc, 0x54, 0x01, 0x76, 0x2b}} , + {{0x02, 0x0c, 0xc2, 0xaf, 0x96, 0x36, 0xfe, 0x4a, 0xe2, 0x54, 0x20, 0x6a, 0xeb, 0xb2, 0x9f, 0x62, 0xd7, 0xce, 0xa2, 0x3f, 0x20, 0x11, 0x34, 0x37, 0xe0, 0x42, 0xed, 0x6f, 0xf9, 0x1a, 0xc8, 0x7d}}}, +{{{0xd8, 0xb9, 0x11, 0xe8, 0x36, 0x3f, 0x42, 0xc1, 0xca, 0xdc, 0xd3, 0xf1, 0xc8, 0x23, 0x3d, 0x4f, 0x51, 0x7b, 0x9d, 0x8d, 0xd8, 0xe4, 0xa0, 0xaa, 0xf3, 0x04, 0xd6, 0x11, 0x93, 0xc8, 0x35, 0x45}} , + {{0x61, 0x36, 0xd6, 0x08, 0x90, 0xbf, 0xa7, 0x7a, 0x97, 0x6c, 0x0f, 0x84, 0xd5, 0x33, 0x2d, 0x37, 0xc9, 0x6a, 0x80, 0x90, 0x3d, 0x0a, 0xa2, 0xaa, 0xe1, 0xb8, 0x84, 0xba, 0x61, 0x36, 0xdd, 0x69}}}, +{{{0x6b, 0xdb, 0x5b, 0x9c, 0xc6, 0x92, 0xbc, 0x23, 0xaf, 0xc5, 0xb8, 0x75, 0xf8, 0x42, 0xfa, 0xd6, 0xb6, 0x84, 0x94, 0x63, 0x98, 0x93, 0x48, 0x78, 0x38, 0xcd, 0xbb, 0x18, 0x34, 0xc3, 0xdb, 0x67}} , + {{0x96, 0xf3, 0x3a, 0x09, 0x56, 0xb0, 0x6f, 0x7c, 0x51, 0x1e, 0x1b, 0x39, 0x48, 0xea, 0xc9, 0x0c, 0x25, 0xa2, 0x7a, 0xca, 0xe7, 0x92, 0xfc, 0x59, 0x30, 0xa3, 0x89, 0x85, 0xdf, 0x6f, 0x43, 0x38}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x79, 0x84, 0x44, 0x19, 0xbd, 0xe9, 0x54, 0xc4, 0xc0, 0x6e, 0x2a, 0xa8, 0xa8, 0x9b, 0x43, 0xd5, 0x71, 0x22, 0x5f, 0xdc, 0x01, 0xfa, 0xdf, 0xb3, 0xb8, 0x47, 0x4b, 0x0a, 0xa5, 0x44, 0xea, 0x29}} , + {{0x05, 0x90, 0x50, 0xaf, 0x63, 0x5f, 0x9d, 0x9e, 0xe1, 0x9d, 0x38, 0x97, 0x1f, 0x6c, 0xac, 0x30, 0x46, 0xb2, 0x6a, 0x19, 0xd1, 0x4b, 0xdb, 0xbb, 0x8c, 0xda, 0x2e, 0xab, 0xc8, 0x5a, 0x77, 0x6c}}}, +{{{0x2b, 0xbe, 0xaf, 0xa1, 0x6d, 0x2f, 0x0b, 0xb1, 0x8f, 0xe3, 0xe0, 0x38, 0xcd, 0x0b, 0x41, 0x1b, 0x4a, 0x15, 0x07, 0xf3, 0x6f, 0xdc, 0xb8, 0xe9, 0xde, 0xb2, 0xa3, 0x40, 0x01, 0xa6, 0x45, 0x1e}} , + {{0x76, 0x0a, 0xda, 0x8d, 0x2c, 0x07, 0x3f, 0x89, 0x7d, 0x04, 0xad, 0x43, 0x50, 0x6e, 0xd2, 0x47, 0xcb, 0x8a, 0xe6, 0x85, 0x1a, 0x24, 0xf3, 0xd2, 0x60, 0xfd, 0xdf, 0x73, 0xa4, 0x0d, 0x73, 0x0e}}}, +{{{0xfd, 0x67, 0x6b, 0x71, 0x9b, 0x81, 0x53, 0x39, 0x39, 0xf4, 0xb8, 0xd5, 0xc3, 0x30, 0x9b, 0x3b, 0x7c, 0xa3, 0xf0, 0xd0, 0x84, 0x21, 0xd6, 0xbf, 0xb7, 0x4c, 0x87, 0x13, 0x45, 0x2d, 0xa7, 0x55}} , + {{0x5d, 0x04, 0xb3, 0x40, 0x28, 0x95, 0x2d, 0x30, 0x83, 0xec, 0x5e, 0xe4, 0xff, 0x75, 0xfe, 0x79, 0x26, 0x9d, 0x1d, 0x36, 0xcd, 0x0a, 0x15, 0xd2, 0x24, 0x14, 0x77, 0x71, 0xd7, 0x8a, 0x1b, 0x04}}}, +{{{0x5d, 0x93, 0xc9, 0xbe, 0xaa, 0x90, 0xcd, 0x9b, 0xfb, 0x73, 0x7e, 0xb0, 0x64, 0x98, 0x57, 0x44, 0x42, 0x41, 0xb1, 0xaf, 0xea, 0xc1, 0xc3, 0x22, 0xff, 0x60, 0x46, 0xcb, 0x61, 0x81, 0x70, 0x61}} , + {{0x0d, 0x82, 0xb9, 0xfe, 0x21, 0xcd, 0xc4, 0xf5, 0x98, 0x0c, 0x4e, 0x72, 0xee, 0x87, 0x49, 0xf8, 0xa1, 0x95, 0xdf, 0x8f, 0x2d, 0xbd, 0x21, 0x06, 0x7c, 0x15, 0xe8, 0x12, 0x6d, 0x93, 0xd6, 0x38}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x91, 0xf7, 0x51, 0xd9, 0xef, 0x7d, 0x42, 0x01, 0x13, 0xe9, 0xb8, 0x7f, 0xa6, 0x49, 0x17, 0x64, 0x21, 0x80, 0x83, 0x2c, 0x63, 0x4c, 0x60, 0x09, 0x59, 0x91, 0x92, 0x77, 0x39, 0x51, 0xf4, 0x48}} , + {{0x60, 0xd5, 0x22, 0x83, 0x08, 0x2f, 0xff, 0x99, 0x3e, 0x69, 0x6d, 0x88, 0xda, 0xe7, 0x5b, 0x52, 0x26, 0x31, 0x2a, 0xe5, 0x89, 0xde, 0x68, 0x90, 0xb6, 0x22, 0x5a, 0xbd, 0xd3, 0x85, 0x53, 0x31}}}, +{{{0xd8, 0xce, 0xdc, 0xf9, 0x3c, 0x4b, 0xa2, 0x1d, 0x2c, 0x2f, 0x36, 0xbe, 0x7a, 0xfc, 0xcd, 0xbc, 0xdc, 0xf9, 0x30, 0xbd, 0xff, 0x05, 0xc7, 0xe4, 0x8e, 0x17, 0x62, 0xf8, 0x4d, 0xa0, 0x56, 0x79}} , + {{0x82, 0xe7, 0xf6, 0xba, 0x53, 0x84, 0x0a, 0xa3, 0x34, 0xff, 0x3c, 0xa3, 0x6a, 0xa1, 0x37, 0xea, 0xdd, 0xb6, 0x95, 0xb3, 0x78, 0x19, 0x76, 0x1e, 0x55, 0x2f, 0x77, 0x2e, 0x7f, 0xc1, 0xea, 0x5e}}}, +{{{0x83, 0xe1, 0x6e, 0xa9, 0x07, 0x33, 0x3e, 0x83, 0xff, 0xcb, 0x1c, 0x9f, 0xb1, 0xa3, 0xb4, 0xc9, 0xe1, 0x07, 0x97, 0xff, 0xf8, 0x23, 0x8f, 0xce, 0x40, 0xfd, 0x2e, 0x5e, 0xdb, 0x16, 0x43, 0x2d}} , + {{0xba, 0x38, 0x02, 0xf7, 0x81, 0x43, 0x83, 0xa3, 0x20, 0x4f, 0x01, 0x3b, 0x8a, 0x04, 0x38, 0x31, 0xc6, 0x0f, 0xc8, 0xdf, 0xd7, 0xfa, 0x2f, 0x88, 0x3f, 0xfc, 0x0c, 0x76, 0xc4, 0xa6, 0x45, 0x72}}}, +{{{0xbb, 0x0c, 0xbc, 0x6a, 0xa4, 0x97, 0x17, 0x93, 0x2d, 0x6f, 0xde, 0x72, 0x10, 0x1c, 0x08, 0x2c, 0x0f, 0x80, 0x32, 0x68, 0x27, 0xd4, 0xab, 0xdd, 0xc5, 0x58, 0x61, 0x13, 0x6d, 0x11, 0x1e, 0x4d}} , + {{0x1a, 0xb9, 0xc9, 0x10, 0xfb, 0x1e, 0x4e, 0xf4, 0x84, 0x4b, 0x8a, 0x5e, 0x7b, 0x4b, 0xe8, 0x43, 0x8c, 0x8f, 0x00, 0xb5, 0x54, 0x13, 0xc5, 0x5c, 0xb6, 0x35, 0x4e, 0x9d, 0xe4, 0x5b, 0x41, 0x6d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x15, 0x7d, 0x12, 0x48, 0x82, 0x14, 0x42, 0xcd, 0x32, 0xd4, 0x4b, 0xc1, 0x72, 0x61, 0x2a, 0x8c, 0xec, 0xe2, 0xf8, 0x24, 0x45, 0x94, 0xe3, 0xbe, 0xdd, 0x67, 0xa8, 0x77, 0x5a, 0xae, 0x5b, 0x4b}} , + {{0xcb, 0x77, 0x9a, 0x20, 0xde, 0xb8, 0x23, 0xd9, 0xa0, 0x0f, 0x8c, 0x7b, 0xa5, 0xcb, 0xae, 0xb6, 0xec, 0x42, 0x67, 0x0e, 0x58, 0xa4, 0x75, 0x98, 0x21, 0x71, 0x84, 0xb3, 0xe0, 0x76, 0x94, 0x73}}}, +{{{0xdf, 0xfc, 0x69, 0x28, 0x23, 0x3f, 0x5b, 0xf8, 0x3b, 0x24, 0x37, 0xf3, 0x1d, 0xd5, 0x22, 0x6b, 0xd0, 0x98, 0xa8, 0x6c, 0xcf, 0xff, 0x06, 0xe1, 0x13, 0xdf, 0xb9, 0xc1, 0x0c, 0xa9, 0xbf, 0x33}} , + {{0xd9, 0x81, 0xda, 0xb2, 0x4f, 0x82, 0x9d, 0x43, 0x81, 0x09, 0xf1, 0xd2, 0x01, 0xef, 0xac, 0xf4, 0x2d, 0x7d, 0x01, 0x09, 0xf1, 0xff, 0xa5, 0x9f, 0xe5, 0xca, 0x27, 0x63, 0xdb, 0x20, 0xb1, 0x53}}}, +{{{0x67, 0x02, 0xe8, 0xad, 0xa9, 0x34, 0xd4, 0xf0, 0x15, 0x81, 0xaa, 0xc7, 0x4d, 0x87, 0x94, 0xea, 0x75, 0xe7, 0x4c, 0x94, 0x04, 0x0e, 0x69, 0x87, 0xe7, 0x51, 0x91, 0x10, 0x03, 0xc7, 0xbe, 0x56}} , + {{0x32, 0xfb, 0x86, 0xec, 0x33, 0x6b, 0x2e, 0x51, 0x2b, 0xc8, 0xfa, 0x6c, 0x70, 0x47, 0x7e, 0xce, 0x05, 0x0c, 0x71, 0xf3, 0xb4, 0x56, 0xa6, 0xdc, 0xcc, 0x78, 0x07, 0x75, 0xd0, 0xdd, 0xb2, 0x6a}}}, +{{{0xc6, 0xef, 0xb9, 0xc0, 0x2b, 0x22, 0x08, 0x1e, 0x71, 0x70, 0xb3, 0x35, 0x9c, 0x7a, 0x01, 0x92, 0x44, 0x9a, 0xf6, 0xb0, 0x58, 0x95, 0xc1, 0x9b, 0x02, 0xed, 0x2d, 0x7c, 0x34, 0x29, 0x49, 0x44}} , + {{0x45, 0x62, 0x1d, 0x2e, 0xff, 0x2a, 0x1c, 0x21, 0xa4, 0x25, 0x7b, 0x0d, 0x8c, 0x15, 0x39, 0xfc, 0x8f, 0x7c, 0xa5, 0x7d, 0x1e, 0x25, 0xa3, 0x45, 0xd6, 0xab, 0xbd, 0xcb, 0xc5, 0x5e, 0x78, 0x77}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd0, 0xd3, 0x42, 0xed, 0x1d, 0x00, 0x3c, 0x15, 0x2c, 0x9c, 0x77, 0x81, 0xd2, 0x73, 0xd1, 0x06, 0xd5, 0xc4, 0x7f, 0x94, 0xbb, 0x92, 0x2d, 0x2c, 0x4b, 0x45, 0x4b, 0xe9, 0x2a, 0x89, 0x6b, 0x2b}} , + {{0xd2, 0x0c, 0x88, 0xc5, 0x48, 0x4d, 0xea, 0x0d, 0x4a, 0xc9, 0x52, 0x6a, 0x61, 0x79, 0xe9, 0x76, 0xf3, 0x85, 0x52, 0x5c, 0x1b, 0x2c, 0xe1, 0xd6, 0xc4, 0x0f, 0x18, 0x0e, 0x4e, 0xf6, 0x1c, 0x7f}}}, +{{{0xb4, 0x04, 0x2e, 0x42, 0xcb, 0x1f, 0x2b, 0x11, 0x51, 0x7b, 0x08, 0xac, 0xaa, 0x3e, 0x9e, 0x52, 0x60, 0xb7, 0xc2, 0x61, 0x57, 0x8c, 0x84, 0xd5, 0x18, 0xa6, 0x19, 0xfc, 0xb7, 0x75, 0x91, 0x1b}} , + {{0xe8, 0x68, 0xca, 0x44, 0xc8, 0x38, 0x38, 0xcc, 0x53, 0x0a, 0x32, 0x35, 0xcc, 0x52, 0xcb, 0x0e, 0xf7, 0xc5, 0xe7, 0xec, 0x3d, 0x85, 0xcc, 0x58, 0xe2, 0x17, 0x47, 0xff, 0x9f, 0xa5, 0x30, 0x17}}}, +{{{0xe3, 0xae, 0xc8, 0xc1, 0x71, 0x75, 0x31, 0x00, 0x37, 0x41, 0x5c, 0x0e, 0x39, 0xda, 0x73, 0xa0, 0xc7, 0x97, 0x36, 0x6c, 0x5b, 0xf2, 0xee, 0x64, 0x0a, 0x3d, 0x89, 0x1e, 0x1d, 0x49, 0x8c, 0x37}} , + {{0x4c, 0xe6, 0xb0, 0xc1, 0xa5, 0x2a, 0x82, 0x09, 0x08, 0xad, 0x79, 0x9c, 0x56, 0xf6, 0xf9, 0xc1, 0xd7, 0x7c, 0x39, 0x7f, 0x93, 0xca, 0x11, 0x55, 0xbf, 0x07, 0x1b, 0x82, 0x29, 0x69, 0x95, 0x5c}}}, +{{{0x87, 0xee, 0xa6, 0x56, 0x9e, 0xc2, 0x9a, 0x56, 0x24, 0x42, 0x85, 0x4d, 0x98, 0x31, 0x1e, 0x60, 0x4d, 0x87, 0x85, 0x04, 0xae, 0x46, 0x12, 0xf9, 0x8e, 0x7f, 0xe4, 0x7f, 0xf6, 0x1c, 0x37, 0x01}} , + {{0x73, 0x4c, 0xb6, 0xc5, 0xc4, 0xe9, 0x6c, 0x85, 0x48, 0x4a, 0x5a, 0xac, 0xd9, 0x1f, 0x43, 0xf8, 0x62, 0x5b, 0xee, 0x98, 0x2a, 0x33, 0x8e, 0x79, 0xce, 0x61, 0x06, 0x35, 0xd8, 0xd7, 0xca, 0x71}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x72, 0xd3, 0xae, 0xa6, 0xca, 0x8f, 0xcd, 0xcc, 0x78, 0x8e, 0x19, 0x4d, 0xa7, 0xd2, 0x27, 0xe9, 0xa4, 0x3c, 0x16, 0x5b, 0x84, 0x80, 0xf9, 0xd0, 0xcc, 0x6a, 0x1e, 0xca, 0x1e, 0x67, 0xbd, 0x63}} , + {{0x7b, 0x6e, 0x2a, 0xd2, 0x87, 0x48, 0xff, 0xa1, 0xca, 0xe9, 0x15, 0x85, 0xdc, 0xdb, 0x2c, 0x39, 0x12, 0x91, 0xa9, 0x20, 0xaa, 0x4f, 0x29, 0xf4, 0x15, 0x7a, 0xd2, 0xf5, 0x32, 0xcc, 0x60, 0x04}}}, +{{{0xe5, 0x10, 0x47, 0x3b, 0xfa, 0x90, 0xfc, 0x30, 0xb5, 0xea, 0x6f, 0x56, 0x8f, 0xfb, 0x0e, 0xa7, 0x3b, 0xc8, 0xb2, 0xff, 0x02, 0x7a, 0x33, 0x94, 0x93, 0x2a, 0x03, 0xe0, 0x96, 0x3a, 0x6c, 0x0f}} , + {{0x5a, 0x63, 0x67, 0xe1, 0x9b, 0x47, 0x78, 0x9f, 0x38, 0x79, 0xac, 0x97, 0x66, 0x1d, 0x5e, 0x51, 0xee, 0x24, 0x42, 0xe8, 0x58, 0x4b, 0x8a, 0x03, 0x75, 0x86, 0x37, 0x86, 0xe2, 0x97, 0x4e, 0x3d}}}, +{{{0x3f, 0x75, 0x8e, 0xb4, 0xff, 0xd8, 0xdd, 0xd6, 0x37, 0x57, 0x9d, 0x6d, 0x3b, 0xbd, 0xd5, 0x60, 0x88, 0x65, 0x9a, 0xb9, 0x4a, 0x68, 0x84, 0xa2, 0x67, 0xdd, 0x17, 0x25, 0x97, 0x04, 0x8b, 0x5e}} , + {{0xbb, 0x40, 0x5e, 0xbc, 0x16, 0x92, 0x05, 0xc4, 0xc0, 0x4e, 0x72, 0x90, 0x0e, 0xab, 0xcf, 0x8a, 0xed, 0xef, 0xb9, 0x2d, 0x3b, 0xf8, 0x43, 0x5b, 0xba, 0x2d, 0xeb, 0x2f, 0x52, 0xd2, 0xd1, 0x5a}}}, +{{{0x40, 0xb4, 0xab, 0xe6, 0xad, 0x9f, 0x46, 0x69, 0x4a, 0xb3, 0x8e, 0xaa, 0xea, 0x9c, 0x8a, 0x20, 0x16, 0x5d, 0x8c, 0x13, 0xbd, 0xf6, 0x1d, 0xc5, 0x24, 0xbd, 0x90, 0x2a, 0x1c, 0xc7, 0x13, 0x3b}} , + {{0x54, 0xdc, 0x16, 0x0d, 0x18, 0xbe, 0x35, 0x64, 0x61, 0x52, 0x02, 0x80, 0xaf, 0x05, 0xf7, 0xa6, 0x42, 0xd3, 0x8f, 0x2e, 0x79, 0x26, 0xa8, 0xbb, 0xb2, 0x17, 0x48, 0xb2, 0x7a, 0x0a, 0x89, 0x14}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x20, 0xa8, 0x88, 0xe3, 0x91, 0xc0, 0x6e, 0xbb, 0x8a, 0x27, 0x82, 0x51, 0x83, 0xb2, 0x28, 0xa9, 0x83, 0xeb, 0xa6, 0xa9, 0x4d, 0x17, 0x59, 0x22, 0x54, 0x00, 0x50, 0x45, 0xcb, 0x48, 0x4b, 0x18}} , + {{0x33, 0x7c, 0xe7, 0x26, 0xba, 0x4d, 0x32, 0xfe, 0x53, 0xf4, 0xfa, 0x83, 0xe3, 0xa5, 0x79, 0x66, 0x73, 0xef, 0x80, 0x23, 0x68, 0xc2, 0x60, 0xdd, 0xa9, 0x33, 0xdc, 0x03, 0x7a, 0xe0, 0xe0, 0x3e}}}, +{{{0x34, 0x5c, 0x13, 0xfb, 0xc0, 0xe3, 0x78, 0x2b, 0x54, 0x58, 0x22, 0x9b, 0x76, 0x81, 0x7f, 0x93, 0x9c, 0x25, 0x3c, 0xd2, 0xe9, 0x96, 0x21, 0x26, 0x08, 0xf5, 0xed, 0x95, 0x11, 0xae, 0x04, 0x5a}} , + {{0xb9, 0xe8, 0xc5, 0x12, 0x97, 0x1f, 0x83, 0xfe, 0x3e, 0x94, 0x99, 0xd4, 0x2d, 0xf9, 0x52, 0x59, 0x5c, 0x82, 0xa6, 0xf0, 0x75, 0x7e, 0xe8, 0xec, 0xcc, 0xac, 0x18, 0x21, 0x09, 0x67, 0x66, 0x67}}}, +{{{0xb3, 0x40, 0x29, 0xd1, 0xcb, 0x1b, 0x08, 0x9e, 0x9c, 0xb7, 0x53, 0xb9, 0x3b, 0x71, 0x08, 0x95, 0x12, 0x1a, 0x58, 0xaf, 0x7e, 0x82, 0x52, 0x43, 0x4f, 0x11, 0x39, 0xf4, 0x93, 0x1a, 0x26, 0x05}} , + {{0x6e, 0x44, 0xa3, 0xf9, 0x64, 0xaf, 0xe7, 0x6d, 0x7d, 0xdf, 0x1e, 0xac, 0x04, 0xea, 0x3b, 0x5f, 0x9b, 0xe8, 0x24, 0x9d, 0x0e, 0xe5, 0x2e, 0x3e, 0xdf, 0xa9, 0xf7, 0xd4, 0x50, 0x71, 0xf0, 0x78}}}, +{{{0x3e, 0xa8, 0x38, 0xc2, 0x57, 0x56, 0x42, 0x9a, 0xb1, 0xe2, 0xf8, 0x45, 0xaa, 0x11, 0x48, 0x5f, 0x17, 0xc4, 0x54, 0x27, 0xdc, 0x5d, 0xaa, 0xdd, 0x41, 0xbc, 0xdf, 0x81, 0xb9, 0x53, 0xee, 0x52}} , + {{0xc3, 0xf1, 0xa7, 0x6d, 0xb3, 0x5f, 0x92, 0x6f, 0xcc, 0x91, 0xb8, 0x95, 0x05, 0xdf, 0x3c, 0x64, 0x57, 0x39, 0x61, 0x51, 0xad, 0x8c, 0x38, 0x7b, 0xc8, 0xde, 0x00, 0x34, 0xbe, 0xa1, 0xb0, 0x7e}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x25, 0x24, 0x1d, 0x8a, 0x67, 0x20, 0xee, 0x42, 0xeb, 0x38, 0xed, 0x0b, 0x8b, 0xcd, 0x46, 0x9d, 0x5e, 0x6b, 0x1e, 0x24, 0x9d, 0x12, 0x05, 0x1a, 0xcc, 0x05, 0x4e, 0x92, 0x38, 0xe1, 0x1f, 0x50}} , + {{0x4e, 0xee, 0x1c, 0x91, 0xe6, 0x11, 0xbd, 0x8e, 0x55, 0x1a, 0x18, 0x75, 0x66, 0xaf, 0x4d, 0x7b, 0x0f, 0xae, 0x6d, 0x85, 0xca, 0x82, 0x58, 0x21, 0x9c, 0x18, 0xe0, 0xed, 0xec, 0x22, 0x80, 0x2f}}}, +{{{0x68, 0x3b, 0x0a, 0x39, 0x1d, 0x6a, 0x15, 0x57, 0xfc, 0xf0, 0x63, 0x54, 0xdb, 0x39, 0xdb, 0xe8, 0x5c, 0x64, 0xff, 0xa0, 0x09, 0x4f, 0x3b, 0xb7, 0x32, 0x60, 0x99, 0x94, 0xfd, 0x94, 0x82, 0x2d}} , + {{0x24, 0xf6, 0x5a, 0x44, 0xf1, 0x55, 0x2c, 0xdb, 0xea, 0x7c, 0x84, 0x7c, 0x01, 0xac, 0xe3, 0xfd, 0xc9, 0x27, 0xc1, 0x5a, 0xb9, 0xde, 0x4f, 0x5a, 0x90, 0xdd, 0xc6, 0x67, 0xaa, 0x6f, 0x8a, 0x3a}}}, +{{{0x78, 0x52, 0x87, 0xc9, 0x97, 0x63, 0xb1, 0xdd, 0x54, 0x5f, 0xc1, 0xf8, 0xf1, 0x06, 0xa6, 0xa8, 0xa3, 0x88, 0x82, 0xd4, 0xcb, 0xa6, 0x19, 0xdd, 0xd1, 0x11, 0x87, 0x08, 0x17, 0x4c, 0x37, 0x2a}} , + {{0xa1, 0x0c, 0xf3, 0x08, 0x43, 0xd9, 0x24, 0x1e, 0x83, 0xa7, 0xdf, 0x91, 0xca, 0xbd, 0x69, 0x47, 0x8d, 0x1b, 0xe2, 0xb9, 0x4e, 0xb5, 0xe1, 0x76, 0xb3, 0x1c, 0x93, 0x03, 0xce, 0x5f, 0xb3, 0x5a}}}, +{{{0x1d, 0xda, 0xe4, 0x61, 0x03, 0x50, 0xa9, 0x8b, 0x68, 0x18, 0xef, 0xb2, 0x1c, 0x84, 0x3b, 0xa2, 0x44, 0x95, 0xa3, 0x04, 0x3b, 0xd6, 0x99, 0x00, 0xaf, 0x76, 0x42, 0x67, 0x02, 0x7d, 0x85, 0x56}} , + {{0xce, 0x72, 0x0e, 0x29, 0x84, 0xb2, 0x7d, 0xd2, 0x45, 0xbe, 0x57, 0x06, 0xed, 0x7f, 0xcf, 0xed, 0xcd, 0xef, 0x19, 0xd6, 0xbc, 0x15, 0x79, 0x64, 0xd2, 0x18, 0xe3, 0x20, 0x67, 0x3a, 0x54, 0x0b}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x52, 0xfd, 0x04, 0xc5, 0xfb, 0x99, 0xe7, 0xe8, 0xfb, 0x8c, 0xe1, 0x42, 0x03, 0xef, 0x9d, 0xd9, 0x9e, 0x4d, 0xf7, 0x80, 0xcf, 0x2e, 0xcc, 0x9b, 0x45, 0xc9, 0x7b, 0x7a, 0xbc, 0x37, 0xa8, 0x52}} , + {{0x96, 0x11, 0x41, 0x8a, 0x47, 0x91, 0xfe, 0xb6, 0xda, 0x7a, 0x54, 0x63, 0xd1, 0x14, 0x35, 0x05, 0x86, 0x8c, 0xa9, 0x36, 0x3f, 0xf2, 0x85, 0x54, 0x4e, 0x92, 0xd8, 0x85, 0x01, 0x46, 0xd6, 0x50}}}, +{{{0x53, 0xcd, 0xf3, 0x86, 0x40, 0xe6, 0x39, 0x42, 0x95, 0xd6, 0xcb, 0x45, 0x1a, 0x20, 0xc8, 0x45, 0x4b, 0x32, 0x69, 0x04, 0xb1, 0xaf, 0x20, 0x46, 0xc7, 0x6b, 0x23, 0x5b, 0x69, 0xee, 0x30, 0x3f}} , + {{0x70, 0x83, 0x47, 0xc0, 0xdb, 0x55, 0x08, 0xa8, 0x7b, 0x18, 0x6d, 0xf5, 0x04, 0x5a, 0x20, 0x0c, 0x4a, 0x8c, 0x60, 0xae, 0xae, 0x0f, 0x64, 0x55, 0x55, 0x2e, 0xd5, 0x1d, 0x53, 0x31, 0x42, 0x41}}}, +{{{0xca, 0xfc, 0x88, 0x6b, 0x96, 0x78, 0x0a, 0x8b, 0x83, 0xdc, 0xbc, 0xaf, 0x40, 0xb6, 0x8d, 0x7f, 0xef, 0xb4, 0xd1, 0x3f, 0xcc, 0xa2, 0x74, 0xc9, 0xc2, 0x92, 0x55, 0x00, 0xab, 0xdb, 0xbf, 0x4f}} , + {{0x93, 0x1c, 0x06, 0x2d, 0x66, 0x65, 0x02, 0xa4, 0x97, 0x18, 0xfd, 0x00, 0xe7, 0xab, 0x03, 0xec, 0xce, 0xc1, 0xbf, 0x37, 0xf8, 0x13, 0x53, 0xa5, 0xe5, 0x0c, 0x3a, 0xa8, 0x55, 0xb9, 0xff, 0x68}}}, +{{{0xe4, 0xe6, 0x6d, 0x30, 0x7d, 0x30, 0x35, 0xc2, 0x78, 0x87, 0xf9, 0xfc, 0x6b, 0x5a, 0xc3, 0xb7, 0x65, 0xd8, 0x2e, 0xc7, 0xa5, 0x0c, 0xc6, 0xdc, 0x12, 0xaa, 0xd6, 0x4f, 0xc5, 0x38, 0xbc, 0x0e}} , + {{0xe2, 0x3c, 0x76, 0x86, 0x38, 0xf2, 0x7b, 0x2c, 0x16, 0x78, 0x8d, 0xf5, 0xa4, 0x15, 0xda, 0xdb, 0x26, 0x85, 0xa0, 0x56, 0xdd, 0x1d, 0xe3, 0xb3, 0xfd, 0x40, 0xef, 0xf2, 0xd9, 0xa1, 0xb3, 0x04}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xdb, 0x49, 0x0e, 0xe6, 0x58, 0x10, 0x7a, 0x52, 0xda, 0xb5, 0x7d, 0x37, 0x6a, 0x3e, 0xa1, 0x78, 0xce, 0xc7, 0x1c, 0x24, 0x23, 0xdb, 0x7d, 0xfb, 0x8c, 0x8d, 0xdc, 0x30, 0x67, 0x69, 0x75, 0x3b}} , + {{0xa9, 0xea, 0x6d, 0x16, 0x16, 0x60, 0xf4, 0x60, 0x87, 0x19, 0x44, 0x8c, 0x4a, 0x8b, 0x3e, 0xfb, 0x16, 0x00, 0x00, 0x54, 0xa6, 0x9e, 0x9f, 0xef, 0xcf, 0xd9, 0xd2, 0x4c, 0x74, 0x31, 0xd0, 0x34}}}, +{{{0xa4, 0xeb, 0x04, 0xa4, 0x8c, 0x8f, 0x71, 0x27, 0x95, 0x85, 0x5d, 0x55, 0x4b, 0xb1, 0x26, 0x26, 0xc8, 0xae, 0x6a, 0x7d, 0xa2, 0x21, 0xca, 0xce, 0x38, 0xab, 0x0f, 0xd0, 0xd5, 0x2b, 0x6b, 0x00}} , + {{0xe5, 0x67, 0x0c, 0xf1, 0x3a, 0x9a, 0xea, 0x09, 0x39, 0xef, 0xd1, 0x30, 0xbc, 0x33, 0xba, 0xb1, 0x6a, 0xc5, 0x27, 0x08, 0x7f, 0x54, 0x80, 0x3d, 0xab, 0xf6, 0x15, 0x7a, 0xc2, 0x40, 0x73, 0x72}}}, +{{{0x84, 0x56, 0x82, 0xb6, 0x12, 0x70, 0x7f, 0xf7, 0xf0, 0xbd, 0x5b, 0xa9, 0xd5, 0xc5, 0x5f, 0x59, 0xbf, 0x7f, 0xb3, 0x55, 0x22, 0x02, 0xc9, 0x44, 0x55, 0x87, 0x8f, 0x96, 0x98, 0x64, 0x6d, 0x15}} , + {{0xb0, 0x8b, 0xaa, 0x1e, 0xec, 0xc7, 0xa5, 0x8f, 0x1f, 0x92, 0x04, 0xc6, 0x05, 0xf6, 0xdf, 0xa1, 0xcc, 0x1f, 0x81, 0xf5, 0x0e, 0x9c, 0x57, 0xdc, 0xe3, 0xbb, 0x06, 0x87, 0x1e, 0xfe, 0x23, 0x6c}}}, +{{{0xd8, 0x2b, 0x5b, 0x16, 0xea, 0x20, 0xf1, 0xd3, 0x68, 0x8f, 0xae, 0x5b, 0xd0, 0xa9, 0x1a, 0x19, 0xa8, 0x36, 0xfb, 0x2b, 0x57, 0x88, 0x7d, 0x90, 0xd5, 0xa6, 0xf3, 0xdc, 0x38, 0x89, 0x4e, 0x1f}} , + {{0xcc, 0x19, 0xda, 0x9b, 0x3b, 0x43, 0x48, 0x21, 0x2e, 0x23, 0x4d, 0x3d, 0xae, 0xf8, 0x8c, 0xfc, 0xdd, 0xa6, 0x74, 0x37, 0x65, 0xca, 0xee, 0x1a, 0x19, 0x8e, 0x9f, 0x64, 0x6f, 0x0c, 0x8b, 0x5a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x25, 0xb9, 0xc2, 0xf0, 0x72, 0xb8, 0x15, 0x16, 0xcc, 0x8d, 0x3c, 0x6f, 0x25, 0xed, 0xf4, 0x46, 0x2e, 0x0c, 0x60, 0x0f, 0xe2, 0x84, 0x34, 0x55, 0x89, 0x59, 0x34, 0x1b, 0xf5, 0x8d, 0xfe, 0x08}} , + {{0xf8, 0xab, 0x93, 0xbc, 0x44, 0xba, 0x1b, 0x75, 0x4b, 0x49, 0x6f, 0xd0, 0x54, 0x2e, 0x63, 0xba, 0xb5, 0xea, 0xed, 0x32, 0x14, 0xc9, 0x94, 0xd8, 0xc5, 0xce, 0xf4, 0x10, 0x68, 0xe0, 0x38, 0x27}}}, +{{{0x74, 0x1c, 0x14, 0x9b, 0xd4, 0x64, 0x61, 0x71, 0x5a, 0xb6, 0x21, 0x33, 0x4f, 0xf7, 0x8e, 0xba, 0xa5, 0x48, 0x9a, 0xc7, 0xfa, 0x9a, 0xf0, 0xb4, 0x62, 0xad, 0xf2, 0x5e, 0xcc, 0x03, 0x24, 0x1a}} , + {{0xf5, 0x76, 0xfd, 0xe4, 0xaf, 0xb9, 0x03, 0x59, 0xce, 0x63, 0xd2, 0x3b, 0x1f, 0xcd, 0x21, 0x0c, 0xad, 0x44, 0xa5, 0x97, 0xac, 0x80, 0x11, 0x02, 0x9b, 0x0c, 0xe5, 0x8b, 0xcd, 0xfb, 0x79, 0x77}}}, +{{{0x15, 0xbe, 0x9a, 0x0d, 0xba, 0x38, 0x72, 0x20, 0x8a, 0xf5, 0xbe, 0x59, 0x93, 0x79, 0xb7, 0xf6, 0x6a, 0x0c, 0x38, 0x27, 0x1a, 0x60, 0xf4, 0x86, 0x3b, 0xab, 0x5a, 0x00, 0xa0, 0xce, 0x21, 0x7d}} , + {{0x6c, 0xba, 0x14, 0xc5, 0xea, 0x12, 0x9e, 0x2e, 0x82, 0x63, 0xce, 0x9b, 0x4a, 0xe7, 0x1d, 0xec, 0xf1, 0x2e, 0x51, 0x1c, 0xf4, 0xd0, 0x69, 0x15, 0x42, 0x9d, 0xa3, 0x3f, 0x0e, 0xbf, 0xe9, 0x5c}}}, +{{{0xe4, 0x0d, 0xf4, 0xbd, 0xee, 0x31, 0x10, 0xed, 0xcb, 0x12, 0x86, 0xad, 0xd4, 0x2f, 0x90, 0x37, 0x32, 0xc3, 0x0b, 0x73, 0xec, 0x97, 0x85, 0xa4, 0x01, 0x1c, 0x76, 0x35, 0xfe, 0x75, 0xdd, 0x71}} , + {{0x11, 0xa4, 0x88, 0x9f, 0x3e, 0x53, 0x69, 0x3b, 0x1b, 0xe0, 0xf7, 0xba, 0x9b, 0xad, 0x4e, 0x81, 0x5f, 0xb5, 0x5c, 0xae, 0xbe, 0x67, 0x86, 0x37, 0x34, 0x8e, 0x07, 0x32, 0x45, 0x4a, 0x67, 0x39}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x90, 0x70, 0x58, 0x20, 0x03, 0x1e, 0x67, 0xb2, 0xc8, 0x9b, 0x58, 0xc5, 0xb1, 0xeb, 0x2d, 0x4a, 0xde, 0x82, 0x8c, 0xf2, 0xd2, 0x14, 0xb8, 0x70, 0x61, 0x4e, 0x73, 0xd6, 0x0b, 0x6b, 0x0d, 0x30}} , + {{0x81, 0xfc, 0x55, 0x5c, 0xbf, 0xa7, 0xc4, 0xbd, 0xe2, 0xf0, 0x4b, 0x8f, 0xe9, 0x7d, 0x99, 0xfa, 0xd3, 0xab, 0xbc, 0xc7, 0x83, 0x2b, 0x04, 0x7f, 0x0c, 0x19, 0x43, 0x03, 0x3d, 0x07, 0xca, 0x40}}}, +{{{0xf9, 0xc8, 0xbe, 0x8c, 0x16, 0x81, 0x39, 0x96, 0xf6, 0x17, 0x58, 0xc8, 0x30, 0x58, 0xfb, 0xc2, 0x03, 0x45, 0xd2, 0x52, 0x76, 0xe0, 0x6a, 0x26, 0x28, 0x5c, 0x88, 0x59, 0x6a, 0x5a, 0x54, 0x42}} , + {{0x07, 0xb5, 0x2e, 0x2c, 0x67, 0x15, 0x9b, 0xfb, 0x83, 0x69, 0x1e, 0x0f, 0xda, 0xd6, 0x29, 0xb1, 0x60, 0xe0, 0xb2, 0xba, 0x69, 0xa2, 0x9e, 0xbd, 0xbd, 0xe0, 0x1c, 0xbd, 0xcd, 0x06, 0x64, 0x70}}}, +{{{0x41, 0xfa, 0x8c, 0xe1, 0x89, 0x8f, 0x27, 0xc8, 0x25, 0x8f, 0x6f, 0x5f, 0x55, 0xf8, 0xde, 0x95, 0x6d, 0x2f, 0x75, 0x16, 0x2b, 0x4e, 0x44, 0xfd, 0x86, 0x6e, 0xe9, 0x70, 0x39, 0x76, 0x97, 0x7e}} , + {{0x17, 0x62, 0x6b, 0x14, 0xa1, 0x7c, 0xd0, 0x79, 0x6e, 0xd8, 0x8a, 0xa5, 0x6d, 0x8c, 0x93, 0xd2, 0x3f, 0xec, 0x44, 0x8d, 0x6e, 0x91, 0x01, 0x8c, 0x8f, 0xee, 0x01, 0x8f, 0xc0, 0xb4, 0x85, 0x0e}}}, +{{{0x02, 0x3a, 0x70, 0x41, 0xe4, 0x11, 0x57, 0x23, 0xac, 0xe6, 0xfc, 0x54, 0x7e, 0xcd, 0xd7, 0x22, 0xcb, 0x76, 0x9f, 0x20, 0xce, 0xa0, 0x73, 0x76, 0x51, 0x3b, 0xa4, 0xf8, 0xe3, 0x62, 0x12, 0x6c}} , + {{0x7f, 0x00, 0x9c, 0x26, 0x0d, 0x6f, 0x48, 0x7f, 0x3a, 0x01, 0xed, 0xc5, 0x96, 0xb0, 0x1f, 0x4f, 0xa8, 0x02, 0x62, 0x27, 0x8a, 0x50, 0x8d, 0x9a, 0x8b, 0x52, 0x0f, 0x1e, 0xcf, 0x41, 0x38, 0x19}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xf5, 0x6c, 0xd4, 0x2f, 0x0f, 0x69, 0x0f, 0x87, 0x3f, 0x61, 0x65, 0x1e, 0x35, 0x34, 0x85, 0xba, 0x02, 0x30, 0xac, 0x25, 0x3d, 0xe2, 0x62, 0xf1, 0xcc, 0xe9, 0x1b, 0xc2, 0xef, 0x6a, 0x42, 0x57}} , + {{0x34, 0x1f, 0x2e, 0xac, 0xd1, 0xc7, 0x04, 0x52, 0x32, 0x66, 0xb2, 0x33, 0x73, 0x21, 0x34, 0x54, 0xf7, 0x71, 0xed, 0x06, 0xb0, 0xff, 0xa6, 0x59, 0x6f, 0x8a, 0x4e, 0xfb, 0x02, 0xb0, 0x45, 0x6b}}}, +{{{0xf5, 0x48, 0x0b, 0x03, 0xc5, 0x22, 0x7d, 0x80, 0x08, 0x53, 0xfe, 0x32, 0xb1, 0xa1, 0x8a, 0x74, 0x6f, 0xbd, 0x3f, 0x85, 0xf4, 0xcf, 0xf5, 0x60, 0xaf, 0x41, 0x7e, 0x3e, 0x46, 0xa3, 0x5a, 0x20}} , + {{0xaa, 0x35, 0x87, 0x44, 0x63, 0x66, 0x97, 0xf8, 0x6e, 0x55, 0x0c, 0x04, 0x3e, 0x35, 0x50, 0xbf, 0x93, 0x69, 0xd2, 0x8b, 0x05, 0x55, 0x99, 0xbe, 0xe2, 0x53, 0x61, 0xec, 0xe8, 0x08, 0x0b, 0x32}}}, +{{{0xb3, 0x10, 0x45, 0x02, 0x69, 0x59, 0x2e, 0x97, 0xd9, 0x64, 0xf8, 0xdb, 0x25, 0x80, 0xdc, 0xc4, 0xd5, 0x62, 0x3c, 0xed, 0x65, 0x91, 0xad, 0xd1, 0x57, 0x81, 0x94, 0xaa, 0xa1, 0x29, 0xfc, 0x68}} , + {{0xdd, 0xb5, 0x7d, 0xab, 0x5a, 0x21, 0x41, 0x53, 0xbb, 0x17, 0x79, 0x0d, 0xd1, 0xa8, 0x0c, 0x0c, 0x20, 0x88, 0x09, 0xe9, 0x84, 0xe8, 0x25, 0x11, 0x67, 0x7a, 0x8b, 0x1a, 0xe4, 0x5d, 0xe1, 0x5d}}}, +{{{0x37, 0xea, 0xfe, 0x65, 0x3b, 0x25, 0xe8, 0xe1, 0xc2, 0xc5, 0x02, 0xa4, 0xbe, 0x98, 0x0a, 0x2b, 0x61, 0xc1, 0x9b, 0xe2, 0xd5, 0x92, 0xe6, 0x9e, 0x7d, 0x1f, 0xca, 0x43, 0x88, 0x8b, 0x2c, 0x59}} , + {{0xe0, 0xb5, 0x00, 0x1d, 0x2a, 0x6f, 0xaf, 0x79, 0x86, 0x2f, 0xa6, 0x5a, 0x93, 0xd1, 0xfe, 0xae, 0x3a, 0xee, 0xdb, 0x7c, 0x61, 0xbe, 0x7c, 0x01, 0xf9, 0xfe, 0x52, 0xdc, 0xd8, 0x52, 0xa3, 0x42}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x22, 0xaf, 0x13, 0x37, 0xbd, 0x37, 0x71, 0xac, 0x04, 0x46, 0x63, 0xac, 0xa4, 0x77, 0xed, 0x25, 0x38, 0xe0, 0x15, 0xa8, 0x64, 0x00, 0x0d, 0xce, 0x51, 0x01, 0xa9, 0xbc, 0x0f, 0x03, 0x1c, 0x04}} , + {{0x89, 0xf9, 0x80, 0x07, 0xcf, 0x3f, 0xb3, 0xe9, 0xe7, 0x45, 0x44, 0x3d, 0x2a, 0x7c, 0xe9, 0xe4, 0x16, 0x5c, 0x5e, 0x65, 0x1c, 0xc7, 0x7d, 0xc6, 0x7a, 0xfb, 0x43, 0xee, 0x25, 0x76, 0x46, 0x72}}}, +{{{0x02, 0xa2, 0xed, 0xf4, 0x8f, 0x6b, 0x0b, 0x3e, 0xeb, 0x35, 0x1a, 0xd5, 0x7e, 0xdb, 0x78, 0x00, 0x96, 0x8a, 0xa0, 0xb4, 0xcf, 0x60, 0x4b, 0xd4, 0xd5, 0xf9, 0x2d, 0xbf, 0x88, 0xbd, 0x22, 0x62}} , + {{0x13, 0x53, 0xe4, 0x82, 0x57, 0xfa, 0x1e, 0x8f, 0x06, 0x2b, 0x90, 0xba, 0x08, 0xb6, 0x10, 0x54, 0x4f, 0x7c, 0x1b, 0x26, 0xed, 0xda, 0x6b, 0xdd, 0x25, 0xd0, 0x4e, 0xea, 0x42, 0xbb, 0x25, 0x03}}}, +{{{0x51, 0x16, 0x50, 0x7c, 0xd5, 0x5d, 0xf6, 0x99, 0xe8, 0x77, 0x72, 0x4e, 0xfa, 0x62, 0xcb, 0x76, 0x75, 0x0c, 0xe2, 0x71, 0x98, 0x92, 0xd5, 0xfa, 0x45, 0xdf, 0x5c, 0x6f, 0x1e, 0x9e, 0x28, 0x69}} , + {{0x0d, 0xac, 0x66, 0x6d, 0xc3, 0x8b, 0xba, 0x16, 0xb5, 0xe2, 0xa0, 0x0d, 0x0c, 0xbd, 0xa4, 0x8e, 0x18, 0x6c, 0xf2, 0xdc, 0xf9, 0xdc, 0x4a, 0x86, 0x25, 0x95, 0x14, 0xcb, 0xd8, 0x1a, 0x04, 0x0f}}}, +{{{0x97, 0xa5, 0xdb, 0x8b, 0x2d, 0xaa, 0x42, 0x11, 0x09, 0xf2, 0x93, 0xbb, 0xd9, 0x06, 0x84, 0x4e, 0x11, 0xa8, 0xa0, 0x25, 0x2b, 0xa6, 0x5f, 0xae, 0xc4, 0xb4, 0x4c, 0xc8, 0xab, 0xc7, 0x3b, 0x02}} , + {{0xee, 0xc9, 0x29, 0x0f, 0xdf, 0x11, 0x85, 0xed, 0xce, 0x0d, 0x62, 0x2c, 0x8f, 0x4b, 0xf9, 0x04, 0xe9, 0x06, 0x72, 0x1d, 0x37, 0x20, 0x50, 0xc9, 0x14, 0xeb, 0xec, 0x39, 0xa7, 0x97, 0x2b, 0x4d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x69, 0xd1, 0x39, 0xbd, 0xfb, 0x33, 0xbe, 0xc4, 0xf0, 0x5c, 0xef, 0xf0, 0x56, 0x68, 0xfc, 0x97, 0x47, 0xc8, 0x72, 0xb6, 0x53, 0xa4, 0x0a, 0x98, 0xa5, 0xb4, 0x37, 0x71, 0xcf, 0x66, 0x50, 0x6d}} , + {{0x17, 0xa4, 0x19, 0x52, 0x11, 0x47, 0xb3, 0x5c, 0x5b, 0xa9, 0x2e, 0x22, 0xb4, 0x00, 0x52, 0xf9, 0x57, 0x18, 0xb8, 0xbe, 0x5a, 0xe3, 0xab, 0x83, 0xc8, 0x87, 0x0a, 0x2a, 0xd8, 0x8c, 0xbb, 0x54}}}, +{{{0xa9, 0x62, 0x93, 0x85, 0xbe, 0xe8, 0x73, 0x4a, 0x0e, 0xb0, 0xb5, 0x2d, 0x94, 0x50, 0xaa, 0xd3, 0xb2, 0xea, 0x9d, 0x62, 0x76, 0x3b, 0x07, 0x34, 0x4e, 0x2d, 0x70, 0xc8, 0x9a, 0x15, 0x66, 0x6b}} , + {{0xc5, 0x96, 0xca, 0xc8, 0x22, 0x1a, 0xee, 0x5f, 0xe7, 0x31, 0x60, 0x22, 0x83, 0x08, 0x63, 0xce, 0xb9, 0x32, 0x44, 0x58, 0x5d, 0x3a, 0x9b, 0xe4, 0x04, 0xd5, 0xef, 0x38, 0xef, 0x4b, 0xdd, 0x19}}}, +{{{0x4d, 0xc2, 0x17, 0x75, 0xa1, 0x68, 0xcd, 0xc3, 0xc6, 0x03, 0x44, 0xe3, 0x78, 0x09, 0x91, 0x47, 0x3f, 0x0f, 0xe4, 0x92, 0x58, 0xfa, 0x7d, 0x1f, 0x20, 0x94, 0x58, 0x5e, 0xbc, 0x19, 0x02, 0x6f}} , + {{0x20, 0xd6, 0xd8, 0x91, 0x54, 0xa7, 0xf3, 0x20, 0x4b, 0x34, 0x06, 0xfa, 0x30, 0xc8, 0x6f, 0x14, 0x10, 0x65, 0x74, 0x13, 0x4e, 0xf0, 0x69, 0x26, 0xce, 0xcf, 0x90, 0xf4, 0xd0, 0xc5, 0xc8, 0x64}}}, +{{{0x26, 0xa2, 0x50, 0x02, 0x24, 0x72, 0xf1, 0xf0, 0x4e, 0x2d, 0x93, 0xd5, 0x08, 0xe7, 0xae, 0x38, 0xf7, 0x18, 0xa5, 0x32, 0x34, 0xc2, 0xf0, 0xa6, 0xec, 0xb9, 0x61, 0x7b, 0x64, 0x99, 0xac, 0x71}} , + {{0x25, 0xcf, 0x74, 0x55, 0x1b, 0xaa, 0xa9, 0x38, 0x41, 0x40, 0xd5, 0x95, 0x95, 0xab, 0x1c, 0x5e, 0xbc, 0x41, 0x7e, 0x14, 0x30, 0xbe, 0x13, 0x89, 0xf4, 0xe5, 0xeb, 0x28, 0xc0, 0xc2, 0x96, 0x3a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2b, 0x77, 0x45, 0xec, 0x67, 0x76, 0x32, 0x4c, 0xb9, 0xdf, 0x25, 0x32, 0x6b, 0xcb, 0xe7, 0x14, 0x61, 0x43, 0xee, 0xba, 0x9b, 0x71, 0xef, 0xd2, 0x48, 0x65, 0xbb, 0x1b, 0x8a, 0x13, 0x1b, 0x22}} , + {{0x84, 0xad, 0x0c, 0x18, 0x38, 0x5a, 0xba, 0xd0, 0x98, 0x59, 0xbf, 0x37, 0xb0, 0x4f, 0x97, 0x60, 0x20, 0xb3, 0x9b, 0x97, 0xf6, 0x08, 0x6c, 0xa4, 0xff, 0xfb, 0xb7, 0xfa, 0x95, 0xb2, 0x51, 0x79}}}, +{{{0x28, 0x5c, 0x3f, 0xdb, 0x6b, 0x18, 0x3b, 0x5c, 0xd1, 0x04, 0x28, 0xde, 0x85, 0x52, 0x31, 0xb5, 0xbb, 0xf6, 0xa9, 0xed, 0xbe, 0x28, 0x4f, 0xb3, 0x7e, 0x05, 0x6a, 0xdb, 0x95, 0x0d, 0x1b, 0x1c}} , + {{0xd5, 0xc5, 0xc3, 0x9a, 0x0a, 0xd0, 0x31, 0x3e, 0x07, 0x36, 0x8e, 0xc0, 0x8a, 0x62, 0xb1, 0xca, 0xd6, 0x0e, 0x1e, 0x9d, 0xef, 0xab, 0x98, 0x4d, 0xbb, 0x6c, 0x05, 0xe0, 0xe4, 0x5d, 0xbd, 0x57}}}, +{{{0xcc, 0x21, 0x27, 0xce, 0xfd, 0xa9, 0x94, 0x8e, 0xe1, 0xab, 0x49, 0xe0, 0x46, 0x26, 0xa1, 0xa8, 0x8c, 0xa1, 0x99, 0x1d, 0xb4, 0x27, 0x6d, 0x2d, 0xc8, 0x39, 0x30, 0x5e, 0x37, 0x52, 0xc4, 0x6e}} , + {{0xa9, 0x85, 0xf4, 0xe7, 0xb0, 0x15, 0x33, 0x84, 0x1b, 0x14, 0x1a, 0x02, 0xd9, 0x3b, 0xad, 0x0f, 0x43, 0x6c, 0xea, 0x3e, 0x0f, 0x7e, 0xda, 0xdd, 0x6b, 0x4c, 0x7f, 0x6e, 0xd4, 0x6b, 0xbf, 0x0f}}}, +{{{0x47, 0x9f, 0x7c, 0x56, 0x7c, 0x43, 0x91, 0x1c, 0xbb, 0x4e, 0x72, 0x3e, 0x64, 0xab, 0xa0, 0xa0, 0xdf, 0xb4, 0xd8, 0x87, 0x3a, 0xbd, 0xa8, 0x48, 0xc9, 0xb8, 0xef, 0x2e, 0xad, 0x6f, 0x84, 0x4f}} , + {{0x2d, 0x2d, 0xf0, 0x1b, 0x7e, 0x2a, 0x6c, 0xf8, 0xa9, 0x6a, 0xe1, 0xf0, 0x99, 0xa1, 0x67, 0x9a, 0xd4, 0x13, 0xca, 0xca, 0xba, 0x27, 0x92, 0xaa, 0xa1, 0x5d, 0x50, 0xde, 0xcc, 0x40, 0x26, 0x0a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x9f, 0x3e, 0xf2, 0xb2, 0x90, 0xce, 0xdb, 0x64, 0x3e, 0x03, 0xdd, 0x37, 0x36, 0x54, 0x70, 0x76, 0x24, 0xb5, 0x69, 0x03, 0xfc, 0xa0, 0x2b, 0x74, 0xb2, 0x05, 0x0e, 0xcc, 0xd8, 0x1f, 0x6a, 0x1f}} , + {{0x19, 0x5e, 0x60, 0x69, 0x58, 0x86, 0xa0, 0x31, 0xbd, 0x32, 0xe9, 0x2c, 0x5c, 0xd2, 0x85, 0xba, 0x40, 0x64, 0xa8, 0x74, 0xf8, 0x0e, 0x1c, 0xb3, 0xa9, 0x69, 0xe8, 0x1e, 0x40, 0x64, 0x99, 0x77}}}, +{{{0x6c, 0x32, 0x4f, 0xfd, 0xbb, 0x5c, 0xbb, 0x8d, 0x64, 0x66, 0x4a, 0x71, 0x1f, 0x79, 0xa3, 0xad, 0x8d, 0xf9, 0xd4, 0xec, 0xcf, 0x67, 0x70, 0xfa, 0x05, 0x4a, 0x0f, 0x6e, 0xaf, 0x87, 0x0a, 0x6f}} , + {{0xc6, 0x36, 0x6e, 0x6c, 0x8c, 0x24, 0x09, 0x60, 0xbe, 0x26, 0xd2, 0x4c, 0x5e, 0x17, 0xca, 0x5f, 0x1d, 0xcc, 0x87, 0xe8, 0x42, 0x6a, 0xcb, 0xcb, 0x7d, 0x92, 0x05, 0x35, 0x81, 0x13, 0x60, 0x6b}}}, +{{{0xf4, 0x15, 0xcd, 0x0f, 0x0a, 0xaf, 0x4e, 0x6b, 0x51, 0xfd, 0x14, 0xc4, 0x2e, 0x13, 0x86, 0x74, 0x44, 0xcb, 0x66, 0x6b, 0xb6, 0x9d, 0x74, 0x56, 0x32, 0xac, 0x8d, 0x8e, 0x8c, 0x8c, 0x8c, 0x39}} , + {{0xca, 0x59, 0x74, 0x1a, 0x11, 0xef, 0x6d, 0xf7, 0x39, 0x5c, 0x3b, 0x1f, 0xfa, 0xe3, 0x40, 0x41, 0x23, 0x9e, 0xf6, 0xd1, 0x21, 0xa2, 0xbf, 0xad, 0x65, 0x42, 0x6b, 0x59, 0x8a, 0xe8, 0xc5, 0x7f}}}, +{{{0x64, 0x05, 0x7a, 0x84, 0x4a, 0x13, 0xc3, 0xf6, 0xb0, 0x6e, 0x9a, 0x6b, 0x53, 0x6b, 0x32, 0xda, 0xd9, 0x74, 0x75, 0xc4, 0xba, 0x64, 0x3d, 0x3b, 0x08, 0xdd, 0x10, 0x46, 0xef, 0xc7, 0x90, 0x1f}} , + {{0x7b, 0x2f, 0x3a, 0xce, 0xc8, 0xa1, 0x79, 0x3c, 0x30, 0x12, 0x44, 0x28, 0xf6, 0xbc, 0xff, 0xfd, 0xf4, 0xc0, 0x97, 0xb0, 0xcc, 0xc3, 0x13, 0x7a, 0xb9, 0x9a, 0x16, 0xe4, 0xcb, 0x4c, 0x34, 0x63}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x07, 0x4e, 0xd3, 0x2d, 0x09, 0x33, 0x0e, 0xd2, 0x0d, 0xbe, 0x3e, 0xe7, 0xe4, 0xaa, 0xb7, 0x00, 0x8b, 0xe8, 0xad, 0xaa, 0x7a, 0x8d, 0x34, 0x28, 0xa9, 0x81, 0x94, 0xc5, 0xe7, 0x42, 0xac, 0x47}} , + {{0x24, 0x89, 0x7a, 0x8f, 0xb5, 0x9b, 0xf0, 0xc2, 0x03, 0x64, 0xd0, 0x1e, 0xf5, 0xa4, 0xb2, 0xf3, 0x74, 0xe9, 0x1a, 0x16, 0xfd, 0xcb, 0x15, 0xea, 0xeb, 0x10, 0x6c, 0x35, 0xd1, 0xc1, 0xa6, 0x28}}}, +{{{0xcc, 0xd5, 0x39, 0xfc, 0xa5, 0xa4, 0xad, 0x32, 0x15, 0xce, 0x19, 0xe8, 0x34, 0x2b, 0x1c, 0x60, 0x91, 0xfc, 0x05, 0xa9, 0xb3, 0xdc, 0x80, 0x29, 0xc4, 0x20, 0x79, 0x06, 0x39, 0xc0, 0xe2, 0x22}} , + {{0xbb, 0xa8, 0xe1, 0x89, 0x70, 0x57, 0x18, 0x54, 0x3c, 0xf6, 0x0d, 0x82, 0x12, 0x05, 0x87, 0x96, 0x06, 0x39, 0xe3, 0xf8, 0xb3, 0x95, 0xe5, 0xd7, 0x26, 0xbf, 0x09, 0x5a, 0x94, 0xf9, 0x1c, 0x63}}}, +{{{0x2b, 0x8c, 0x2d, 0x9a, 0x8b, 0x84, 0xf2, 0x56, 0xfb, 0xad, 0x2e, 0x7f, 0xb7, 0xfc, 0x30, 0xe1, 0x35, 0x89, 0xba, 0x4d, 0xa8, 0x6d, 0xce, 0x8c, 0x8b, 0x30, 0xe0, 0xda, 0x29, 0x18, 0x11, 0x17}} , + {{0x19, 0xa6, 0x5a, 0x65, 0x93, 0xc3, 0xb5, 0x31, 0x22, 0x4f, 0xf3, 0xf6, 0x0f, 0xeb, 0x28, 0xc3, 0x7c, 0xeb, 0xce, 0x86, 0xec, 0x67, 0x76, 0x6e, 0x35, 0x45, 0x7b, 0xd8, 0x6b, 0x92, 0x01, 0x65}}}, +{{{0x3d, 0xd5, 0x9a, 0x64, 0x73, 0x36, 0xb1, 0xd6, 0x86, 0x98, 0x42, 0x3f, 0x8a, 0xf1, 0xc7, 0xf5, 0x42, 0xa8, 0x9c, 0x52, 0xa8, 0xdc, 0xf9, 0x24, 0x3f, 0x4a, 0xa1, 0xa4, 0x5b, 0xe8, 0x62, 0x1a}} , + {{0xc5, 0xbd, 0xc8, 0x14, 0xd5, 0x0d, 0xeb, 0xe1, 0xa5, 0xe6, 0x83, 0x11, 0x09, 0x00, 0x1d, 0x55, 0x83, 0x51, 0x7e, 0x75, 0x00, 0x81, 0xb9, 0xcb, 0xd8, 0xc5, 0xe5, 0xa1, 0xd9, 0x17, 0x6d, 0x1f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xea, 0xf9, 0xe4, 0xe9, 0xe1, 0x52, 0x3f, 0x51, 0x19, 0x0d, 0xdd, 0xd9, 0x9d, 0x93, 0x31, 0x87, 0x23, 0x09, 0xd5, 0x83, 0xeb, 0x92, 0x09, 0x76, 0x6e, 0xe3, 0xf8, 0xc0, 0xa2, 0x66, 0xb5, 0x36}} , + {{0x3a, 0xbb, 0x39, 0xed, 0x32, 0x02, 0xe7, 0x43, 0x7a, 0x38, 0x14, 0x84, 0xe3, 0x44, 0xd2, 0x5e, 0x94, 0xdd, 0x78, 0x89, 0x55, 0x4c, 0x73, 0x9e, 0xe1, 0xe4, 0x3e, 0x43, 0xd0, 0x4a, 0xde, 0x1b}}}, +{{{0xb2, 0xe7, 0x8f, 0xe3, 0xa3, 0xc5, 0xcb, 0x72, 0xee, 0x79, 0x41, 0xf8, 0xdf, 0xee, 0x65, 0xc5, 0x45, 0x77, 0x27, 0x3c, 0xbd, 0x58, 0xd3, 0x75, 0xe2, 0x04, 0x4b, 0xbb, 0x65, 0xf3, 0xc8, 0x0f}} , + {{0x24, 0x7b, 0x93, 0x34, 0xb5, 0xe2, 0x74, 0x48, 0xcd, 0xa0, 0x0b, 0x92, 0x97, 0x66, 0x39, 0xf4, 0xb0, 0xe2, 0x5d, 0x39, 0x6a, 0x5b, 0x45, 0x17, 0x78, 0x1e, 0xdb, 0x91, 0x81, 0x1c, 0xf9, 0x16}}}, +{{{0x16, 0xdf, 0xd1, 0x5a, 0xd5, 0xe9, 0x4e, 0x58, 0x95, 0x93, 0x5f, 0x51, 0x09, 0xc3, 0x2a, 0xc9, 0xd4, 0x55, 0x48, 0x79, 0xa4, 0xa3, 0xb2, 0xc3, 0x62, 0xaa, 0x8c, 0xe8, 0xad, 0x47, 0x39, 0x1b}} , + {{0x46, 0xda, 0x9e, 0x51, 0x3a, 0xe6, 0xd1, 0xa6, 0xbb, 0x4d, 0x7b, 0x08, 0xbe, 0x8c, 0xd5, 0xf3, 0x3f, 0xfd, 0xf7, 0x44, 0x80, 0x2d, 0x53, 0x4b, 0xd0, 0x87, 0x68, 0xc1, 0xb5, 0xd8, 0xf7, 0x07}}}, +{{{0xf4, 0x10, 0x46, 0xbe, 0xb7, 0xd2, 0xd1, 0xce, 0x5e, 0x76, 0xa2, 0xd7, 0x03, 0xdc, 0xe4, 0x81, 0x5a, 0xf6, 0x3c, 0xde, 0xae, 0x7a, 0x9d, 0x21, 0x34, 0xa5, 0xf6, 0xa9, 0x73, 0xe2, 0x8d, 0x60}} , + {{0xfa, 0x44, 0x71, 0xf6, 0x41, 0xd8, 0xc6, 0x58, 0x13, 0x37, 0xeb, 0x84, 0x0f, 0x96, 0xc7, 0xdc, 0xc8, 0xa9, 0x7a, 0x83, 0xb2, 0x2f, 0x31, 0xb1, 0x1a, 0xd8, 0x98, 0x3f, 0x11, 0xd0, 0x31, 0x3b}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x81, 0xd5, 0x34, 0x16, 0x01, 0xa3, 0x93, 0xea, 0x52, 0x94, 0xec, 0x93, 0xb7, 0x81, 0x11, 0x2d, 0x58, 0xf9, 0xb5, 0x0a, 0xaa, 0x4f, 0xf6, 0x2e, 0x3f, 0x36, 0xbf, 0x33, 0x5a, 0xe7, 0xd1, 0x08}} , + {{0x1a, 0xcf, 0x42, 0xae, 0xcc, 0xb5, 0x77, 0x39, 0xc4, 0x5b, 0x5b, 0xd0, 0x26, 0x59, 0x27, 0xd0, 0x55, 0x71, 0x12, 0x9d, 0x88, 0x3d, 0x9c, 0xea, 0x41, 0x6a, 0xf0, 0x50, 0x93, 0x93, 0xdd, 0x47}}}, +{{{0x6f, 0xc9, 0x51, 0x6d, 0x1c, 0xaa, 0xf5, 0xa5, 0x90, 0x3f, 0x14, 0xe2, 0x6e, 0x8e, 0x64, 0xfd, 0xac, 0xe0, 0x4e, 0x22, 0xe5, 0xc1, 0xbc, 0x29, 0x0a, 0x6a, 0x9e, 0xa1, 0x60, 0xcb, 0x2f, 0x0b}} , + {{0xdc, 0x39, 0x32, 0xf3, 0xa1, 0x44, 0xe9, 0xc5, 0xc3, 0x78, 0xfb, 0x95, 0x47, 0x34, 0x35, 0x34, 0xe8, 0x25, 0xde, 0x93, 0xc6, 0xb4, 0x76, 0x6d, 0x86, 0x13, 0xc6, 0xe9, 0x68, 0xb5, 0x01, 0x63}}}, +{{{0x1f, 0x9a, 0x52, 0x64, 0x97, 0xd9, 0x1c, 0x08, 0x51, 0x6f, 0x26, 0x9d, 0xaa, 0x93, 0x33, 0x43, 0xfa, 0x77, 0xe9, 0x62, 0x9b, 0x5d, 0x18, 0x75, 0xeb, 0x78, 0xf7, 0x87, 0x8f, 0x41, 0xb4, 0x4d}} , + {{0x13, 0xa8, 0x82, 0x3e, 0xe9, 0x13, 0xad, 0xeb, 0x01, 0xca, 0xcf, 0xda, 0xcd, 0xf7, 0x6c, 0xc7, 0x7a, 0xdc, 0x1e, 0x6e, 0xc8, 0x4e, 0x55, 0x62, 0x80, 0xea, 0x78, 0x0c, 0x86, 0xb9, 0x40, 0x51}}}, +{{{0x27, 0xae, 0xd3, 0x0d, 0x4c, 0x8f, 0x34, 0xea, 0x7d, 0x3c, 0xe5, 0x8a, 0xcf, 0x5b, 0x92, 0xd8, 0x30, 0x16, 0xb4, 0xa3, 0x75, 0xff, 0xeb, 0x27, 0xc8, 0x5c, 0x6c, 0xc2, 0xee, 0x6c, 0x21, 0x0b}} , + {{0xc3, 0xba, 0x12, 0x53, 0x2a, 0xaa, 0x77, 0xad, 0x19, 0x78, 0x55, 0x8a, 0x2e, 0x60, 0x87, 0xc2, 0x6e, 0x91, 0x38, 0x91, 0x3f, 0x7a, 0xc5, 0x24, 0x8f, 0x51, 0xc5, 0xde, 0xb0, 0x53, 0x30, 0x56}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x02, 0xfe, 0x54, 0x12, 0x18, 0xca, 0x7d, 0xa5, 0x68, 0x43, 0xa3, 0x6d, 0x14, 0x2a, 0x6a, 0xa5, 0x8e, 0x32, 0xe7, 0x63, 0x4f, 0xe3, 0xc6, 0x44, 0x3e, 0xab, 0x63, 0xca, 0x17, 0x86, 0x74, 0x3f}} , + {{0x1e, 0x64, 0xc1, 0x7d, 0x52, 0xdc, 0x13, 0x5a, 0xa1, 0x9c, 0x4e, 0xee, 0x99, 0x28, 0xbb, 0x4c, 0xee, 0xac, 0xa9, 0x1b, 0x89, 0xa2, 0x38, 0x39, 0x7b, 0xc4, 0x0f, 0x42, 0xe6, 0x89, 0xed, 0x0f}}}, +{{{0xf3, 0x3c, 0x8c, 0x80, 0x83, 0x10, 0x8a, 0x37, 0x50, 0x9c, 0xb4, 0xdf, 0x3f, 0x8c, 0xf7, 0x23, 0x07, 0xd6, 0xff, 0xa0, 0x82, 0x6c, 0x75, 0x3b, 0xe4, 0xb5, 0xbb, 0xe4, 0xe6, 0x50, 0xf0, 0x08}} , + {{0x62, 0xee, 0x75, 0x48, 0x92, 0x33, 0xf2, 0xf4, 0xad, 0x15, 0x7a, 0xa1, 0x01, 0x46, 0xa9, 0x32, 0x06, 0x88, 0xb6, 0x36, 0x47, 0x35, 0xb9, 0xb4, 0x42, 0x85, 0x76, 0xf0, 0x48, 0x00, 0x90, 0x38}}}, +{{{0x51, 0x15, 0x9d, 0xc3, 0x95, 0xd1, 0x39, 0xbb, 0x64, 0x9d, 0x15, 0x81, 0xc1, 0x68, 0xd0, 0xb6, 0xa4, 0x2c, 0x7d, 0x5e, 0x02, 0x39, 0x00, 0xe0, 0x3b, 0xa4, 0xcc, 0xca, 0x1d, 0x81, 0x24, 0x10}} , + {{0xe7, 0x29, 0xf9, 0x37, 0xd9, 0x46, 0x5a, 0xcd, 0x70, 0xfe, 0x4d, 0x5b, 0xbf, 0xa5, 0xcf, 0x91, 0xf4, 0xef, 0xee, 0x8a, 0x29, 0xd0, 0xe7, 0xc4, 0x25, 0x92, 0x8a, 0xff, 0x36, 0xfc, 0xe4, 0x49}}}, +{{{0xbd, 0x00, 0xb9, 0x04, 0x7d, 0x35, 0xfc, 0xeb, 0xd0, 0x0b, 0x05, 0x32, 0x52, 0x7a, 0x89, 0x24, 0x75, 0x50, 0xe1, 0x63, 0x02, 0x82, 0x8e, 0xe7, 0x85, 0x0c, 0xf2, 0x56, 0x44, 0x37, 0x83, 0x25}} , + {{0x8f, 0xa1, 0xce, 0xcb, 0x60, 0xda, 0x12, 0x02, 0x1e, 0x29, 0x39, 0x2a, 0x03, 0xb7, 0xeb, 0x77, 0x40, 0xea, 0xc9, 0x2b, 0x2c, 0xd5, 0x7d, 0x7e, 0x2c, 0xc7, 0x5a, 0xfd, 0xff, 0xc4, 0xd1, 0x62}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x1d, 0x88, 0x98, 0x5b, 0x4e, 0xfc, 0x41, 0x24, 0x05, 0xe6, 0x50, 0x2b, 0xae, 0x96, 0x51, 0xd9, 0x6b, 0x72, 0xb2, 0x33, 0x42, 0x98, 0x68, 0xbb, 0x10, 0x5a, 0x7a, 0x8c, 0x9d, 0x07, 0xb4, 0x05}} , + {{0x2f, 0x61, 0x9f, 0xd7, 0xa8, 0x3f, 0x83, 0x8c, 0x10, 0x69, 0x90, 0xe6, 0xcf, 0xd2, 0x63, 0xa3, 0xe4, 0x54, 0x7e, 0xe5, 0x69, 0x13, 0x1c, 0x90, 0x57, 0xaa, 0xe9, 0x53, 0x22, 0x43, 0x29, 0x23}}}, +{{{0xe5, 0x1c, 0xf8, 0x0a, 0xfd, 0x2d, 0x7e, 0xf5, 0xf5, 0x70, 0x7d, 0x41, 0x6b, 0x11, 0xfe, 0xbe, 0x99, 0xd1, 0x55, 0x29, 0x31, 0xbf, 0xc0, 0x97, 0x6c, 0xd5, 0x35, 0xcc, 0x5e, 0x8b, 0xd9, 0x69}} , + {{0x8e, 0x4e, 0x9f, 0x25, 0xf8, 0x81, 0x54, 0x2d, 0x0e, 0xd5, 0x54, 0x81, 0x9b, 0xa6, 0x92, 0xce, 0x4b, 0xe9, 0x8f, 0x24, 0x3b, 0xca, 0xe0, 0x44, 0xab, 0x36, 0xfe, 0xfb, 0x87, 0xd4, 0x26, 0x3e}}}, +{{{0x0f, 0x93, 0x9c, 0x11, 0xe7, 0xdb, 0xf1, 0xf0, 0x85, 0x43, 0x28, 0x15, 0x37, 0xdd, 0xde, 0x27, 0xdf, 0xad, 0x3e, 0x49, 0x4f, 0xe0, 0x5b, 0xf6, 0x80, 0x59, 0x15, 0x3c, 0x85, 0xb7, 0x3e, 0x12}} , + {{0xf5, 0xff, 0xcc, 0xf0, 0xb4, 0x12, 0x03, 0x5f, 0xc9, 0x84, 0xcb, 0x1d, 0x17, 0xe0, 0xbc, 0xcc, 0x03, 0x62, 0xa9, 0x8b, 0x94, 0xa6, 0xaa, 0x18, 0xcb, 0x27, 0x8d, 0x49, 0xa6, 0x17, 0x15, 0x07}}}, +{{{0xd9, 0xb6, 0xd4, 0x9d, 0xd4, 0x6a, 0xaf, 0x70, 0x07, 0x2c, 0x10, 0x9e, 0xbd, 0x11, 0xad, 0xe4, 0x26, 0x33, 0x70, 0x92, 0x78, 0x1c, 0x74, 0x9f, 0x75, 0x60, 0x56, 0xf4, 0x39, 0xa8, 0xa8, 0x62}} , + {{0x3b, 0xbf, 0x55, 0x35, 0x61, 0x8b, 0x44, 0x97, 0xe8, 0x3a, 0x55, 0xc1, 0xc8, 0x3b, 0xfd, 0x95, 0x29, 0x11, 0x60, 0x96, 0x1e, 0xcb, 0x11, 0x9d, 0xc2, 0x03, 0x8a, 0x1b, 0xc6, 0xd6, 0x45, 0x3d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x7e, 0x0e, 0x50, 0xb2, 0xcc, 0x0d, 0x6b, 0xa6, 0x71, 0x5b, 0x42, 0xed, 0xbd, 0xaf, 0xac, 0xf0, 0xfc, 0x12, 0xa2, 0x3f, 0x4e, 0xda, 0xe8, 0x11, 0xf3, 0x23, 0xe1, 0x04, 0x62, 0x03, 0x1c, 0x4e}} , + {{0xc8, 0xb1, 0x1b, 0x6f, 0x73, 0x61, 0x3d, 0x27, 0x0d, 0x7d, 0x7a, 0x25, 0x5f, 0x73, 0x0e, 0x2f, 0x93, 0xf6, 0x24, 0xd8, 0x4f, 0x90, 0xac, 0xa2, 0x62, 0x0a, 0xf0, 0x61, 0xd9, 0x08, 0x59, 0x6a}}}, +{{{0x6f, 0x2d, 0x55, 0xf8, 0x2f, 0x8e, 0xf0, 0x18, 0x3b, 0xea, 0xdd, 0x26, 0x72, 0xd1, 0xf5, 0xfe, 0xe5, 0xb8, 0xe6, 0xd3, 0x10, 0x48, 0x46, 0x49, 0x3a, 0x9f, 0x5e, 0x45, 0x6b, 0x90, 0xe8, 0x7f}} , + {{0xd3, 0x76, 0x69, 0x33, 0x7b, 0xb9, 0x40, 0x70, 0xee, 0xa6, 0x29, 0x6b, 0xdd, 0xd0, 0x5d, 0x8d, 0xc1, 0x3e, 0x4a, 0xea, 0x37, 0xb1, 0x03, 0x02, 0x03, 0x35, 0xf1, 0x28, 0x9d, 0xff, 0x00, 0x13}}}, +{{{0x7a, 0xdb, 0x12, 0xd2, 0x8a, 0x82, 0x03, 0x1b, 0x1e, 0xaf, 0xf9, 0x4b, 0x9c, 0xbe, 0xae, 0x7c, 0xe4, 0x94, 0x2a, 0x23, 0xb3, 0x62, 0x86, 0xe7, 0xfd, 0x23, 0xaa, 0x99, 0xbd, 0x2b, 0x11, 0x6c}} , + {{0x8d, 0xa6, 0xd5, 0xac, 0x9d, 0xcc, 0x68, 0x75, 0x7f, 0xc3, 0x4d, 0x4b, 0xdd, 0x6c, 0xbb, 0x11, 0x5a, 0x60, 0xe5, 0xbd, 0x7d, 0x27, 0x8b, 0xda, 0xb4, 0x95, 0xf6, 0x03, 0x27, 0xa4, 0x92, 0x3f}}}, +{{{0x22, 0xd6, 0xb5, 0x17, 0x84, 0xbf, 0x12, 0xcc, 0x23, 0x14, 0x4a, 0xdf, 0x14, 0x31, 0xbc, 0xa1, 0xac, 0x6e, 0xab, 0xfa, 0x57, 0x11, 0x53, 0xb3, 0x27, 0xe6, 0xf9, 0x47, 0x33, 0x44, 0x34, 0x1e}} , + {{0x79, 0xfc, 0xa6, 0xb4, 0x0b, 0x35, 0x20, 0xc9, 0x4d, 0x22, 0x84, 0xc4, 0xa9, 0x20, 0xec, 0x89, 0x94, 0xba, 0x66, 0x56, 0x48, 0xb9, 0x87, 0x7f, 0xca, 0x1e, 0x06, 0xed, 0xa5, 0x55, 0x59, 0x29}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x56, 0xe1, 0xf5, 0xf1, 0xd5, 0xab, 0xa8, 0x2b, 0xae, 0x89, 0xf3, 0xcf, 0x56, 0x9f, 0xf2, 0x4b, 0x31, 0xbc, 0x18, 0xa9, 0x06, 0x5b, 0xbe, 0xb4, 0x61, 0xf8, 0xb2, 0x06, 0x9c, 0x81, 0xab, 0x4c}} , + {{0x1f, 0x68, 0x76, 0x01, 0x16, 0x38, 0x2b, 0x0f, 0x77, 0x97, 0x92, 0x67, 0x4e, 0x86, 0x6a, 0x8b, 0xe5, 0xe8, 0x0c, 0xf7, 0x36, 0x39, 0xb5, 0x33, 0xe6, 0xcf, 0x5e, 0xbd, 0x18, 0xfb, 0x10, 0x1f}}}, +{{{0x83, 0xf0, 0x0d, 0x63, 0xef, 0x53, 0x6b, 0xb5, 0x6b, 0xf9, 0x83, 0xcf, 0xde, 0x04, 0x22, 0x9b, 0x2c, 0x0a, 0xe0, 0xa5, 0xd8, 0xc7, 0x9c, 0xa5, 0xa3, 0xf6, 0x6f, 0xcf, 0x90, 0x6b, 0x68, 0x7c}} , + {{0x33, 0x15, 0xd7, 0x7f, 0x1a, 0xd5, 0x21, 0x58, 0xc4, 0x18, 0xa5, 0xf0, 0xcc, 0x73, 0xa8, 0xfd, 0xfa, 0x18, 0xd1, 0x03, 0x91, 0x8d, 0x52, 0xd2, 0xa3, 0xa4, 0xd3, 0xb1, 0xea, 0x1d, 0x0f, 0x00}}}, +{{{0xcc, 0x48, 0x83, 0x90, 0xe5, 0xfd, 0x3f, 0x84, 0xaa, 0xf9, 0x8b, 0x82, 0x59, 0x24, 0x34, 0x68, 0x4f, 0x1c, 0x23, 0xd9, 0xcc, 0x71, 0xe1, 0x7f, 0x8c, 0xaf, 0xf1, 0xee, 0x00, 0xb6, 0xa0, 0x77}} , + {{0xf5, 0x1a, 0x61, 0xf7, 0x37, 0x9d, 0x00, 0xf4, 0xf2, 0x69, 0x6f, 0x4b, 0x01, 0x85, 0x19, 0x45, 0x4d, 0x7f, 0x02, 0x7c, 0x6a, 0x05, 0x47, 0x6c, 0x1f, 0x81, 0x20, 0xd4, 0xe8, 0x50, 0x27, 0x72}}}, +{{{0x2c, 0x3a, 0xe5, 0xad, 0xf4, 0xdd, 0x2d, 0xf7, 0x5c, 0x44, 0xb5, 0x5b, 0x21, 0xa3, 0x89, 0x5f, 0x96, 0x45, 0xca, 0x4d, 0xa4, 0x21, 0x99, 0x70, 0xda, 0xc4, 0xc4, 0xa0, 0xe5, 0xf4, 0xec, 0x0a}} , + {{0x07, 0x68, 0x21, 0x65, 0xe9, 0x08, 0xa0, 0x0b, 0x6a, 0x4a, 0xba, 0xb5, 0x80, 0xaf, 0xd0, 0x1b, 0xc5, 0xf5, 0x4b, 0x73, 0x50, 0x60, 0x2d, 0x71, 0x69, 0x61, 0x0e, 0xc0, 0x20, 0x40, 0x30, 0x19}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd0, 0x75, 0x57, 0x3b, 0xeb, 0x5c, 0x14, 0x56, 0x50, 0xc9, 0x4f, 0xb8, 0xb8, 0x1e, 0xa3, 0xf4, 0xab, 0xf5, 0xa9, 0x20, 0x15, 0x94, 0x82, 0xda, 0x96, 0x1c, 0x9b, 0x59, 0x8c, 0xff, 0xf4, 0x51}} , + {{0xc1, 0x3a, 0x86, 0xd7, 0xb0, 0x06, 0x84, 0x7f, 0x1b, 0xbd, 0xd4, 0x07, 0x78, 0x80, 0x2e, 0xb1, 0xb4, 0xee, 0x52, 0x38, 0xee, 0x9a, 0xf9, 0xf6, 0xf3, 0x41, 0x6e, 0xd4, 0x88, 0x95, 0xac, 0x35}}}, +{{{0x41, 0x97, 0xbf, 0x71, 0x6a, 0x9b, 0x72, 0xec, 0xf3, 0xf8, 0x6b, 0xe6, 0x0e, 0x6c, 0x69, 0xa5, 0x2f, 0x68, 0x52, 0xd8, 0x61, 0x81, 0xc0, 0x63, 0x3f, 0xa6, 0x3c, 0x13, 0x90, 0xe6, 0x8d, 0x56}} , + {{0xe8, 0x39, 0x30, 0x77, 0x23, 0xb1, 0xfd, 0x1b, 0x3d, 0x3e, 0x74, 0x4d, 0x7f, 0xae, 0x5b, 0x3a, 0xb4, 0x65, 0x0e, 0x3a, 0x43, 0xdc, 0xdc, 0x41, 0x47, 0xe6, 0xe8, 0x92, 0x09, 0x22, 0x48, 0x4c}}}, +{{{0x85, 0x57, 0x9f, 0xb5, 0xc8, 0x06, 0xb2, 0x9f, 0x47, 0x3f, 0xf0, 0xfa, 0xe6, 0xa9, 0xb1, 0x9b, 0x6f, 0x96, 0x7d, 0xf9, 0xa4, 0x65, 0x09, 0x75, 0x32, 0xa6, 0x6c, 0x7f, 0x47, 0x4b, 0x2f, 0x4f}} , + {{0x34, 0xe9, 0x59, 0x93, 0x9d, 0x26, 0x80, 0x54, 0xf2, 0xcc, 0x3c, 0xc2, 0x25, 0x85, 0xe3, 0x6a, 0xc1, 0x62, 0x04, 0xa7, 0x08, 0x32, 0x6d, 0xa1, 0x39, 0x84, 0x8a, 0x3b, 0x87, 0x5f, 0x11, 0x13}}}, +{{{0xda, 0x03, 0x34, 0x66, 0xc4, 0x0c, 0x73, 0x6e, 0xbc, 0x24, 0xb5, 0xf9, 0x70, 0x81, 0x52, 0xe9, 0xf4, 0x7c, 0x23, 0xdd, 0x9f, 0xb8, 0x46, 0xef, 0x1d, 0x22, 0x55, 0x7d, 0x71, 0xc4, 0x42, 0x33}} , + {{0xc5, 0x37, 0x69, 0x5b, 0xa8, 0xc6, 0x9d, 0xa4, 0xfc, 0x61, 0x6e, 0x68, 0x46, 0xea, 0xd7, 0x1c, 0x67, 0xd2, 0x7d, 0xfa, 0xf1, 0xcc, 0x54, 0x8d, 0x36, 0x35, 0xc9, 0x00, 0xdf, 0x6c, 0x67, 0x50}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x9a, 0x4d, 0x42, 0x29, 0x5d, 0xa4, 0x6b, 0x6f, 0xa8, 0x8a, 0x4d, 0x91, 0x7b, 0xd2, 0xdf, 0x36, 0xef, 0x01, 0x22, 0xc5, 0xcc, 0x8d, 0xeb, 0x58, 0x3d, 0xb3, 0x50, 0xfc, 0x8b, 0x97, 0x96, 0x33}} , + {{0x93, 0x33, 0x07, 0xc8, 0x4a, 0xca, 0xd0, 0xb1, 0xab, 0xbd, 0xdd, 0xa7, 0x7c, 0xac, 0x3e, 0x45, 0xcb, 0xcc, 0x07, 0x91, 0xbf, 0x35, 0x9d, 0xcb, 0x7d, 0x12, 0x3c, 0x11, 0x59, 0x13, 0xcf, 0x5c}}}, +{{{0x45, 0xb8, 0x41, 0xd7, 0xab, 0x07, 0x15, 0x00, 0x8e, 0xce, 0xdf, 0xb2, 0x43, 0x5c, 0x01, 0xdc, 0xf4, 0x01, 0x51, 0x95, 0x10, 0x5a, 0xf6, 0x24, 0x24, 0xa0, 0x19, 0x3a, 0x09, 0x2a, 0xaa, 0x3f}} , + {{0xdc, 0x8e, 0xeb, 0xc6, 0xbf, 0xdd, 0x11, 0x7b, 0xe7, 0x47, 0xe6, 0xce, 0xe7, 0xb6, 0xc5, 0xe8, 0x8a, 0xdc, 0x4b, 0x57, 0x15, 0x3b, 0x66, 0xca, 0x89, 0xa3, 0xfd, 0xac, 0x0d, 0xe1, 0x1d, 0x7a}}}, +{{{0x89, 0xef, 0xbf, 0x03, 0x75, 0xd0, 0x29, 0x50, 0xcb, 0x7d, 0xd6, 0xbe, 0xad, 0x5f, 0x7b, 0x00, 0x32, 0xaa, 0x98, 0xed, 0x3f, 0x8f, 0x92, 0xcb, 0x81, 0x56, 0x01, 0x63, 0x64, 0xa3, 0x38, 0x39}} , + {{0x8b, 0xa4, 0xd6, 0x50, 0xb4, 0xaa, 0x5d, 0x64, 0x64, 0x76, 0x2e, 0xa1, 0xa6, 0xb3, 0xb8, 0x7c, 0x7a, 0x56, 0xf5, 0x5c, 0x4e, 0x84, 0x5c, 0xfb, 0xdd, 0xca, 0x48, 0x8b, 0x48, 0xb9, 0xba, 0x34}}}, +{{{0xc5, 0xe3, 0xe8, 0xae, 0x17, 0x27, 0xe3, 0x64, 0x60, 0x71, 0x47, 0x29, 0x02, 0x0f, 0x92, 0x5d, 0x10, 0x93, 0xc8, 0x0e, 0xa1, 0xed, 0xba, 0xa9, 0x96, 0x1c, 0xc5, 0x76, 0x30, 0xcd, 0xf9, 0x30}} , + {{0x95, 0xb0, 0xbd, 0x8c, 0xbc, 0xa7, 0x4f, 0x7e, 0xfd, 0x4e, 0x3a, 0xbf, 0x5f, 0x04, 0x79, 0x80, 0x2b, 0x5a, 0x9f, 0x4f, 0x68, 0x21, 0x19, 0x71, 0xc6, 0x20, 0x01, 0x42, 0xaa, 0xdf, 0xae, 0x2c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x90, 0x6e, 0x7e, 0x4b, 0x71, 0x93, 0xc0, 0x72, 0xed, 0xeb, 0x71, 0x24, 0x97, 0x26, 0x9c, 0xfe, 0xcb, 0x3e, 0x59, 0x19, 0xa8, 0x0f, 0x75, 0x7d, 0xbe, 0x18, 0xe6, 0x96, 0x1e, 0x95, 0x70, 0x60}} , + {{0x89, 0x66, 0x3e, 0x1d, 0x4c, 0x5f, 0xfe, 0xc0, 0x04, 0x43, 0xd6, 0x44, 0x19, 0xb5, 0xad, 0xc7, 0x22, 0xdc, 0x71, 0x28, 0x64, 0xde, 0x41, 0x38, 0x27, 0x8f, 0x2c, 0x6b, 0x08, 0xb8, 0xb8, 0x7b}}}, +{{{0x3d, 0x70, 0x27, 0x9d, 0xd9, 0xaf, 0xb1, 0x27, 0xaf, 0xe3, 0x5d, 0x1e, 0x3a, 0x30, 0x54, 0x61, 0x60, 0xe8, 0xc3, 0x26, 0x3a, 0xbc, 0x7e, 0xf5, 0x81, 0xdd, 0x64, 0x01, 0x04, 0xeb, 0xc0, 0x1e}} , + {{0xda, 0x2c, 0xa4, 0xd1, 0xa1, 0xc3, 0x5c, 0x6e, 0x32, 0x07, 0x1f, 0xb8, 0x0e, 0x19, 0x9e, 0x99, 0x29, 0x33, 0x9a, 0xae, 0x7a, 0xed, 0x68, 0x42, 0x69, 0x7c, 0x07, 0xb3, 0x38, 0x2c, 0xf6, 0x3d}}}, +{{{0x64, 0xaa, 0xb5, 0x88, 0x79, 0x65, 0x38, 0x8c, 0x94, 0xd6, 0x62, 0x37, 0x7d, 0x64, 0xcd, 0x3a, 0xeb, 0xff, 0xe8, 0x81, 0x09, 0xc7, 0x6a, 0x50, 0x09, 0x0d, 0x28, 0x03, 0x0d, 0x9a, 0x93, 0x0a}} , + {{0x42, 0xa3, 0xf1, 0xc5, 0xb4, 0x0f, 0xd8, 0xc8, 0x8d, 0x15, 0x31, 0xbd, 0xf8, 0x07, 0x8b, 0xcd, 0x08, 0x8a, 0xfb, 0x18, 0x07, 0xfe, 0x8e, 0x52, 0x86, 0xef, 0xbe, 0xec, 0x49, 0x52, 0x99, 0x08}}}, +{{{0x0f, 0xa9, 0xd5, 0x01, 0xaa, 0x48, 0x4f, 0x28, 0x66, 0x32, 0x1a, 0xba, 0x7c, 0xea, 0x11, 0x80, 0x17, 0x18, 0x9b, 0x56, 0x88, 0x25, 0x06, 0x69, 0x12, 0x2c, 0xea, 0x56, 0x69, 0x41, 0x24, 0x19}} , + {{0xde, 0x21, 0xf0, 0xda, 0x8a, 0xfb, 0xb1, 0xb8, 0xcd, 0xc8, 0x6a, 0x82, 0x19, 0x73, 0xdb, 0xc7, 0xcf, 0x88, 0xeb, 0x96, 0xee, 0x6f, 0xfb, 0x06, 0xd2, 0xcd, 0x7d, 0x7b, 0x12, 0x28, 0x8e, 0x0c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x93, 0x44, 0x97, 0xce, 0x28, 0xff, 0x3a, 0x40, 0xc4, 0xf5, 0xf6, 0x9b, 0xf4, 0x6b, 0x07, 0x84, 0xfb, 0x98, 0xd8, 0xec, 0x8c, 0x03, 0x57, 0xec, 0x49, 0xed, 0x63, 0xb6, 0xaa, 0xff, 0x98, 0x28}} , + {{0x3d, 0x16, 0x35, 0xf3, 0x46, 0xbc, 0xb3, 0xf4, 0xc6, 0xb6, 0x4f, 0xfa, 0xf4, 0xa0, 0x13, 0xe6, 0x57, 0x45, 0x93, 0xb9, 0xbc, 0xd6, 0x59, 0xe7, 0x77, 0x94, 0x6c, 0xab, 0x96, 0x3b, 0x4f, 0x09}}}, +{{{0x5a, 0xf7, 0x6b, 0x01, 0x12, 0x4f, 0x51, 0xc1, 0x70, 0x84, 0x94, 0x47, 0xb2, 0x01, 0x6c, 0x71, 0xd7, 0xcc, 0x17, 0x66, 0x0f, 0x59, 0x5d, 0x5d, 0x10, 0x01, 0x57, 0x11, 0xf5, 0xdd, 0xe2, 0x34}} , + {{0x26, 0xd9, 0x1f, 0x5c, 0x58, 0xac, 0x8b, 0x03, 0xd2, 0xc3, 0x85, 0x0f, 0x3a, 0xc3, 0x7f, 0x6d, 0x8e, 0x86, 0xcd, 0x52, 0x74, 0x8f, 0x55, 0x77, 0x17, 0xb7, 0x8e, 0xb7, 0x88, 0xea, 0xda, 0x1b}}}, +{{{0xb6, 0xea, 0x0e, 0x40, 0x93, 0x20, 0x79, 0x35, 0x6a, 0x61, 0x84, 0x5a, 0x07, 0x6d, 0xf9, 0x77, 0x6f, 0xed, 0x69, 0x1c, 0x0d, 0x25, 0x76, 0xcc, 0xf0, 0xdb, 0xbb, 0xc5, 0xad, 0xe2, 0x26, 0x57}} , + {{0xcf, 0xe8, 0x0e, 0x6b, 0x96, 0x7d, 0xed, 0x27, 0xd1, 0x3c, 0xa9, 0xd9, 0x50, 0xa9, 0x98, 0x84, 0x5e, 0x86, 0xef, 0xd6, 0xf0, 0xf8, 0x0e, 0x89, 0x05, 0x2f, 0xd9, 0x5f, 0x15, 0x5f, 0x73, 0x79}}}, +{{{0xc8, 0x5c, 0x16, 0xfe, 0xed, 0x9f, 0x26, 0x56, 0xf6, 0x4b, 0x9f, 0xa7, 0x0a, 0x85, 0xfe, 0xa5, 0x8c, 0x87, 0xdd, 0x98, 0xce, 0x4e, 0xc3, 0x58, 0x55, 0xb2, 0x7b, 0x3d, 0xd8, 0x6b, 0xb5, 0x4c}} , + {{0x65, 0x38, 0xa0, 0x15, 0xfa, 0xa7, 0xb4, 0x8f, 0xeb, 0xc4, 0x86, 0x9b, 0x30, 0xa5, 0x5e, 0x4d, 0xea, 0x8a, 0x9a, 0x9f, 0x1a, 0xd8, 0x5b, 0x53, 0x14, 0x19, 0x25, 0x63, 0xb4, 0x6f, 0x1f, 0x5d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xac, 0x8f, 0xbc, 0x1e, 0x7d, 0x8b, 0x5a, 0x0b, 0x8d, 0xaf, 0x76, 0x2e, 0x71, 0xe3, 0x3b, 0x6f, 0x53, 0x2f, 0x3e, 0x90, 0x95, 0xd4, 0x35, 0x14, 0x4f, 0x8c, 0x3c, 0xce, 0x57, 0x1c, 0x76, 0x49}} , + {{0xa8, 0x50, 0xe1, 0x61, 0x6b, 0x57, 0x35, 0xeb, 0x44, 0x0b, 0x0c, 0x6e, 0xf9, 0x25, 0x80, 0x74, 0xf2, 0x8f, 0x6f, 0x7a, 0x3e, 0x7f, 0x2d, 0xf3, 0x4e, 0x09, 0x65, 0x10, 0x5e, 0x03, 0x25, 0x32}}}, +{{{0xa9, 0x60, 0xdc, 0x0f, 0x64, 0xe5, 0x1d, 0xe2, 0x8d, 0x4f, 0x79, 0x2f, 0x0e, 0x24, 0x02, 0x00, 0x05, 0x77, 0x43, 0x25, 0x3d, 0x6a, 0xc7, 0xb7, 0xbf, 0x04, 0x08, 0x65, 0xf4, 0x39, 0x4b, 0x65}} , + {{0x96, 0x19, 0x12, 0x6b, 0x6a, 0xb7, 0xe3, 0xdc, 0x45, 0x9b, 0xdb, 0xb4, 0xa8, 0xae, 0xdc, 0xa8, 0x14, 0x44, 0x65, 0x62, 0xce, 0x34, 0x9a, 0x84, 0x18, 0x12, 0x01, 0xf1, 0xe2, 0x7b, 0xce, 0x50}}}, +{{{0x41, 0x21, 0x30, 0x53, 0x1b, 0x47, 0x01, 0xb7, 0x18, 0xd8, 0x82, 0x57, 0xbd, 0xa3, 0x60, 0xf0, 0x32, 0xf6, 0x5b, 0xf0, 0x30, 0x88, 0x91, 0x59, 0xfd, 0x90, 0xa2, 0xb9, 0x55, 0x93, 0x21, 0x34}} , + {{0x97, 0x67, 0x9e, 0xeb, 0x6a, 0xf9, 0x6e, 0xd6, 0x73, 0xe8, 0x6b, 0x29, 0xec, 0x63, 0x82, 0x00, 0xa8, 0x99, 0x1c, 0x1d, 0x30, 0xc8, 0x90, 0x52, 0x90, 0xb6, 0x6a, 0x80, 0x4e, 0xff, 0x4b, 0x51}}}, +{{{0x0f, 0x7d, 0x63, 0x8c, 0x6e, 0x5c, 0xde, 0x30, 0xdf, 0x65, 0xfa, 0x2e, 0xb0, 0xa3, 0x25, 0x05, 0x54, 0xbd, 0x25, 0xba, 0x06, 0xae, 0xdf, 0x8b, 0xd9, 0x1b, 0xea, 0x38, 0xb3, 0x05, 0x16, 0x09}} , + {{0xc7, 0x8c, 0xbf, 0x64, 0x28, 0xad, 0xf8, 0xa5, 0x5a, 0x6f, 0xc9, 0xba, 0xd5, 0x7f, 0xd5, 0xd6, 0xbd, 0x66, 0x2f, 0x3d, 0xaa, 0x54, 0xf6, 0xba, 0x32, 0x22, 0x9a, 0x1e, 0x52, 0x05, 0xf4, 0x1d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xaa, 0x1f, 0xbb, 0xeb, 0xfe, 0xe4, 0x87, 0xfc, 0xb1, 0x2c, 0xb7, 0x88, 0xf4, 0xc6, 0xb9, 0xf5, 0x24, 0x46, 0xf2, 0xa5, 0x9f, 0x8f, 0x8a, 0x93, 0x70, 0x69, 0xd4, 0x56, 0xec, 0xfd, 0x06, 0x46}} , + {{0x4e, 0x66, 0xcf, 0x4e, 0x34, 0xce, 0x0c, 0xd9, 0xa6, 0x50, 0xd6, 0x5e, 0x95, 0xaf, 0xe9, 0x58, 0xfa, 0xee, 0x9b, 0xb8, 0xa5, 0x0f, 0x35, 0xe0, 0x43, 0x82, 0x6d, 0x65, 0xe6, 0xd9, 0x00, 0x0f}}}, +{{{0x7b, 0x75, 0x3a, 0xfc, 0x64, 0xd3, 0x29, 0x7e, 0xdd, 0x49, 0x9a, 0x59, 0x53, 0xbf, 0xb4, 0xa7, 0x52, 0xb3, 0x05, 0xab, 0xc3, 0xaf, 0x16, 0x1a, 0x85, 0x42, 0x32, 0xa2, 0x86, 0xfa, 0x39, 0x43}} , + {{0x0e, 0x4b, 0xa3, 0x63, 0x8a, 0xfe, 0xa5, 0x58, 0xf1, 0x13, 0xbd, 0x9d, 0xaa, 0x7f, 0x76, 0x40, 0x70, 0x81, 0x10, 0x75, 0x99, 0xbb, 0xbe, 0x0b, 0x16, 0xe9, 0xba, 0x62, 0x34, 0xcc, 0x07, 0x6d}}}, +{{{0xc3, 0xf1, 0xc6, 0x93, 0x65, 0xee, 0x0b, 0xbc, 0xea, 0x14, 0xf0, 0xc1, 0xf8, 0x84, 0x89, 0xc2, 0xc9, 0xd7, 0xea, 0x34, 0xca, 0xa7, 0xc4, 0x99, 0xd5, 0x50, 0x69, 0xcb, 0xd6, 0x21, 0x63, 0x7c}} , + {{0x99, 0xeb, 0x7c, 0x31, 0x73, 0x64, 0x67, 0x7f, 0x0c, 0x66, 0xaa, 0x8c, 0x69, 0x91, 0xe2, 0x26, 0xd3, 0x23, 0xe2, 0x76, 0x5d, 0x32, 0x52, 0xdf, 0x5d, 0xc5, 0x8f, 0xb7, 0x7c, 0x84, 0xb3, 0x70}}}, +{{{0xeb, 0x01, 0xc7, 0x36, 0x97, 0x4e, 0xb6, 0xab, 0x5f, 0x0d, 0x2c, 0xba, 0x67, 0x64, 0x55, 0xde, 0xbc, 0xff, 0xa6, 0xec, 0x04, 0xd3, 0x8d, 0x39, 0x56, 0x5e, 0xee, 0xf8, 0xe4, 0x2e, 0x33, 0x62}} , + {{0x65, 0xef, 0xb8, 0x9f, 0xc8, 0x4b, 0xa7, 0xfd, 0x21, 0x49, 0x9b, 0x92, 0x35, 0x82, 0xd6, 0x0a, 0x9b, 0xf2, 0x79, 0xf1, 0x47, 0x2f, 0x6a, 0x7e, 0x9f, 0xcf, 0x18, 0x02, 0x3c, 0xfb, 0x1b, 0x3e}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2f, 0x8b, 0xc8, 0x40, 0x51, 0xd1, 0xac, 0x1a, 0x0b, 0xe4, 0xa9, 0xa2, 0x42, 0x21, 0x19, 0x2f, 0x7b, 0x97, 0xbf, 0xf7, 0x57, 0x6d, 0x3f, 0x3d, 0x4f, 0x0f, 0xe2, 0xb2, 0x81, 0x00, 0x9e, 0x7b}} , + {{0x8c, 0x85, 0x2b, 0xc4, 0xfc, 0xf1, 0xab, 0xe8, 0x79, 0x22, 0xc4, 0x84, 0x17, 0x3a, 0xfa, 0x86, 0xa6, 0x7d, 0xf9, 0xf3, 0x6f, 0x03, 0x57, 0x20, 0x4d, 0x79, 0xf9, 0x6e, 0x71, 0x54, 0x38, 0x09}}}, +{{{0x40, 0x29, 0x74, 0xa8, 0x2f, 0x5e, 0xf9, 0x79, 0xa4, 0xf3, 0x3e, 0xb9, 0xfd, 0x33, 0x31, 0xac, 0x9a, 0x69, 0x88, 0x1e, 0x77, 0x21, 0x2d, 0xf3, 0x91, 0x52, 0x26, 0x15, 0xb2, 0xa6, 0xcf, 0x7e}} , + {{0xc6, 0x20, 0x47, 0x6c, 0xa4, 0x7d, 0xcb, 0x63, 0xea, 0x5b, 0x03, 0xdf, 0x3e, 0x88, 0x81, 0x6d, 0xce, 0x07, 0x42, 0x18, 0x60, 0x7e, 0x7b, 0x55, 0xfe, 0x6a, 0xf3, 0xda, 0x5c, 0x8b, 0x95, 0x10}}}, +{{{0x62, 0xe4, 0x0d, 0x03, 0xb4, 0xd7, 0xcd, 0xfa, 0xbd, 0x46, 0xdf, 0x93, 0x71, 0x10, 0x2c, 0xa8, 0x3b, 0xb6, 0x09, 0x05, 0x70, 0x84, 0x43, 0x29, 0xa8, 0x59, 0xf5, 0x8e, 0x10, 0xe4, 0xd7, 0x20}} , + {{0x57, 0x82, 0x1c, 0xab, 0xbf, 0x62, 0x70, 0xe8, 0xc4, 0xcf, 0xf0, 0x28, 0x6e, 0x16, 0x3c, 0x08, 0x78, 0x89, 0x85, 0x46, 0x0f, 0xf6, 0x7f, 0xcf, 0xcb, 0x7e, 0xb8, 0x25, 0xe9, 0x5a, 0xfa, 0x03}}}, +{{{0xfb, 0x95, 0x92, 0x63, 0x50, 0xfc, 0x62, 0xf0, 0xa4, 0x5e, 0x8c, 0x18, 0xc2, 0x17, 0x24, 0xb7, 0x78, 0xc2, 0xa9, 0xe7, 0x6a, 0x32, 0xd6, 0x29, 0x85, 0xaf, 0xcb, 0x8d, 0x91, 0x13, 0xda, 0x6b}} , + {{0x36, 0x0a, 0xc2, 0xb6, 0x4b, 0xa5, 0x5d, 0x07, 0x17, 0x41, 0x31, 0x5f, 0x62, 0x46, 0xf8, 0x92, 0xf9, 0x66, 0x48, 0x73, 0xa6, 0x97, 0x0d, 0x7d, 0x88, 0xee, 0x62, 0xb1, 0x03, 0xa8, 0x3f, 0x2c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x4a, 0xb1, 0x70, 0x8a, 0xa9, 0xe8, 0x63, 0x79, 0x00, 0xe2, 0x25, 0x16, 0xca, 0x4b, 0x0f, 0xa4, 0x66, 0xad, 0x19, 0x9f, 0x88, 0x67, 0x0c, 0x8b, 0xc2, 0x4a, 0x5b, 0x2b, 0x6d, 0x95, 0xaf, 0x19}} , + {{0x8b, 0x9d, 0xb6, 0xcc, 0x60, 0xb4, 0x72, 0x4f, 0x17, 0x69, 0x5a, 0x4a, 0x68, 0x34, 0xab, 0xa1, 0x45, 0x32, 0x3c, 0x83, 0x87, 0x72, 0x30, 0x54, 0x77, 0x68, 0xae, 0xfb, 0xb5, 0x8b, 0x22, 0x5e}}}, +{{{0xf1, 0xb9, 0x87, 0x35, 0xc5, 0xbb, 0xb9, 0xcf, 0xf5, 0xd6, 0xcd, 0xd5, 0x0c, 0x7c, 0x0e, 0xe6, 0x90, 0x34, 0xfb, 0x51, 0x42, 0x1e, 0x6d, 0xac, 0x9a, 0x46, 0xc4, 0x97, 0x29, 0x32, 0xbf, 0x45}} , + {{0x66, 0x9e, 0xc6, 0x24, 0xc0, 0xed, 0xa5, 0x5d, 0x88, 0xd4, 0xf0, 0x73, 0x97, 0x7b, 0xea, 0x7f, 0x42, 0xff, 0x21, 0xa0, 0x9b, 0x2f, 0x9a, 0xfd, 0x53, 0x57, 0x07, 0x84, 0x48, 0x88, 0x9d, 0x52}}}, +{{{0xc6, 0x96, 0x48, 0x34, 0x2a, 0x06, 0xaf, 0x94, 0x3d, 0xf4, 0x1a, 0xcf, 0xf2, 0xc0, 0x21, 0xc2, 0x42, 0x5e, 0xc8, 0x2f, 0x35, 0xa2, 0x3e, 0x29, 0xfa, 0x0c, 0x84, 0xe5, 0x89, 0x72, 0x7c, 0x06}} , + {{0x32, 0x65, 0x03, 0xe5, 0x89, 0xa6, 0x6e, 0xb3, 0x5b, 0x8e, 0xca, 0xeb, 0xfe, 0x22, 0x56, 0x8b, 0x5d, 0x14, 0x4b, 0x4d, 0xf9, 0xbe, 0xb5, 0xf5, 0xe6, 0x5c, 0x7b, 0x8b, 0xf4, 0x13, 0x11, 0x34}}}, +{{{0x07, 0xc6, 0x22, 0x15, 0xe2, 0x9c, 0x60, 0xa2, 0x19, 0xd9, 0x27, 0xae, 0x37, 0x4e, 0xa6, 0xc9, 0x80, 0xa6, 0x91, 0x8f, 0x12, 0x49, 0xe5, 0x00, 0x18, 0x47, 0xd1, 0xd7, 0x28, 0x22, 0x63, 0x39}} , + {{0xe8, 0xe2, 0x00, 0x7e, 0xf2, 0x9e, 0x1e, 0x99, 0x39, 0x95, 0x04, 0xbd, 0x1e, 0x67, 0x7b, 0xb2, 0x26, 0xac, 0xe6, 0xaa, 0xe2, 0x46, 0xd5, 0xe4, 0xe8, 0x86, 0xbd, 0xab, 0x7c, 0x55, 0x59, 0x6f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x24, 0x64, 0x6e, 0x9b, 0x35, 0x71, 0x78, 0xce, 0x33, 0x03, 0x21, 0x33, 0x36, 0xf1, 0x73, 0x9b, 0xb9, 0x15, 0x8b, 0x2c, 0x69, 0xcf, 0x4d, 0xed, 0x4f, 0x4d, 0x57, 0x14, 0x13, 0x82, 0xa4, 0x4d}} , + {{0x65, 0x6e, 0x0a, 0xa4, 0x59, 0x07, 0x17, 0xf2, 0x6b, 0x4a, 0x1f, 0x6e, 0xf6, 0xb5, 0xbc, 0x62, 0xe4, 0xb6, 0xda, 0xa2, 0x93, 0xbc, 0x29, 0x05, 0xd2, 0xd2, 0x73, 0x46, 0x03, 0x16, 0x40, 0x31}}}, +{{{0x4c, 0x73, 0x6d, 0x15, 0xbd, 0xa1, 0x4d, 0x5c, 0x13, 0x0b, 0x24, 0x06, 0x98, 0x78, 0x1c, 0x5b, 0xeb, 0x1f, 0x18, 0x54, 0x43, 0xd9, 0x55, 0x66, 0xda, 0x29, 0x21, 0xe8, 0xb8, 0x3c, 0x42, 0x22}} , + {{0xb4, 0xcd, 0x08, 0x6f, 0x15, 0x23, 0x1a, 0x0b, 0x22, 0xed, 0xd1, 0xf1, 0xa7, 0xc7, 0x73, 0x45, 0xf3, 0x9e, 0xce, 0x76, 0xb7, 0xf6, 0x39, 0xb6, 0x8e, 0x79, 0xbe, 0xe9, 0x9b, 0xcf, 0x7d, 0x62}}}, +{{{0x92, 0x5b, 0xfc, 0x72, 0xfd, 0xba, 0xf1, 0xfd, 0xa6, 0x7c, 0x95, 0xe3, 0x61, 0x3f, 0xe9, 0x03, 0xd4, 0x2b, 0xd4, 0x20, 0xd9, 0xdb, 0x4d, 0x32, 0x3e, 0xf5, 0x11, 0x64, 0xe3, 0xb4, 0xbe, 0x32}} , + {{0x86, 0x17, 0x90, 0xe7, 0xc9, 0x1f, 0x10, 0xa5, 0x6a, 0x2d, 0x39, 0xd0, 0x3b, 0xc4, 0xa6, 0xe9, 0x59, 0x13, 0xda, 0x1a, 0xe6, 0xa0, 0xb9, 0x3c, 0x50, 0xb8, 0x40, 0x7c, 0x15, 0x36, 0x5a, 0x42}}}, +{{{0xb4, 0x0b, 0x32, 0xab, 0xdc, 0x04, 0x51, 0x55, 0x21, 0x1e, 0x0b, 0x75, 0x99, 0x89, 0x73, 0x35, 0x3a, 0x91, 0x2b, 0xfe, 0xe7, 0x49, 0xea, 0x76, 0xc1, 0xf9, 0x46, 0xb9, 0x53, 0x02, 0x23, 0x04}} , + {{0xfc, 0x5a, 0x1e, 0x1d, 0x74, 0x58, 0x95, 0xa6, 0x8f, 0x7b, 0x97, 0x3e, 0x17, 0x3b, 0x79, 0x2d, 0xa6, 0x57, 0xef, 0x45, 0x02, 0x0b, 0x4d, 0x6e, 0x9e, 0x93, 0x8d, 0x2f, 0xd9, 0x9d, 0xdb, 0x04}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xc0, 0xd7, 0x56, 0x97, 0x58, 0x91, 0xde, 0x09, 0x4f, 0x9f, 0xbe, 0x63, 0xb0, 0x83, 0x86, 0x43, 0x5d, 0xbc, 0xe0, 0xf3, 0xc0, 0x75, 0xbf, 0x8b, 0x8e, 0xaa, 0xf7, 0x8b, 0x64, 0x6e, 0xb0, 0x63}} , + {{0x16, 0xae, 0x8b, 0xe0, 0x9b, 0x24, 0x68, 0x5c, 0x44, 0xc2, 0xd0, 0x08, 0xb7, 0x7b, 0x62, 0xfd, 0x7f, 0xd8, 0xd4, 0xb7, 0x50, 0xfd, 0x2c, 0x1b, 0xbf, 0x41, 0x95, 0xd9, 0x8e, 0xd8, 0x17, 0x1b}}}, +{{{0x86, 0x55, 0x37, 0x8e, 0xc3, 0x38, 0x48, 0x14, 0xb5, 0x97, 0xd2, 0xa7, 0x54, 0x45, 0xf1, 0x35, 0x44, 0x38, 0x9e, 0xf1, 0x1b, 0xb6, 0x34, 0x00, 0x3c, 0x96, 0xee, 0x29, 0x00, 0xea, 0x2c, 0x0b}} , + {{0xea, 0xda, 0x99, 0x9e, 0x19, 0x83, 0x66, 0x6d, 0xe9, 0x76, 0x87, 0x50, 0xd1, 0xfd, 0x3c, 0x60, 0x87, 0xc6, 0x41, 0xd9, 0x8e, 0xdb, 0x5e, 0xde, 0xaa, 0x9a, 0xd3, 0x28, 0xda, 0x95, 0xea, 0x47}}}, +{{{0xd0, 0x80, 0xba, 0x19, 0xae, 0x1d, 0xa9, 0x79, 0xf6, 0x3f, 0xac, 0x5d, 0x6f, 0x96, 0x1f, 0x2a, 0xce, 0x29, 0xb2, 0xff, 0x37, 0xf1, 0x94, 0x8f, 0x0c, 0xb5, 0x28, 0xba, 0x9a, 0x21, 0xf6, 0x66}} , + {{0x02, 0xfb, 0x54, 0xb8, 0x05, 0xf3, 0x81, 0x52, 0x69, 0x34, 0x46, 0x9d, 0x86, 0x76, 0x8f, 0xd7, 0xf8, 0x6a, 0x66, 0xff, 0xe6, 0xa7, 0x90, 0xf7, 0x5e, 0xcd, 0x6a, 0x9b, 0x55, 0xfc, 0x9d, 0x48}}}, +{{{0xbd, 0xaa, 0x13, 0xe6, 0xcd, 0x45, 0x4a, 0xa4, 0x59, 0x0a, 0x64, 0xb1, 0x98, 0xd6, 0x34, 0x13, 0x04, 0xe6, 0x97, 0x94, 0x06, 0xcb, 0xd4, 0x4e, 0xbb, 0x96, 0xcd, 0xd1, 0x57, 0xd1, 0xe3, 0x06}} , + {{0x7a, 0x6c, 0x45, 0x27, 0xc4, 0x93, 0x7f, 0x7d, 0x7c, 0x62, 0x50, 0x38, 0x3a, 0x6b, 0xb5, 0x88, 0xc6, 0xd9, 0xf1, 0x78, 0x19, 0xb9, 0x39, 0x93, 0x3d, 0xc9, 0xe0, 0x9c, 0x3c, 0xce, 0xf5, 0x72}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x24, 0xea, 0x23, 0x7d, 0x56, 0x2c, 0xe2, 0x59, 0x0e, 0x85, 0x60, 0x04, 0x88, 0x5a, 0x74, 0x1e, 0x4b, 0xef, 0x13, 0xda, 0x4c, 0xff, 0x83, 0x45, 0x85, 0x3f, 0x08, 0x95, 0x2c, 0x20, 0x13, 0x1f}} , + {{0x48, 0x5f, 0x27, 0x90, 0x5c, 0x02, 0x42, 0xad, 0x78, 0x47, 0x5c, 0xb5, 0x7e, 0x08, 0x85, 0x00, 0xfa, 0x7f, 0xfd, 0xfd, 0xe7, 0x09, 0x11, 0xf2, 0x7e, 0x1b, 0x38, 0x6c, 0x35, 0x6d, 0x33, 0x66}}}, +{{{0x93, 0x03, 0x36, 0x81, 0xac, 0xe4, 0x20, 0x09, 0x35, 0x4c, 0x45, 0xb2, 0x1e, 0x4c, 0x14, 0x21, 0xe6, 0xe9, 0x8a, 0x7b, 0x8d, 0xfe, 0x1e, 0xc6, 0x3e, 0xc1, 0x35, 0xfa, 0xe7, 0x70, 0x4e, 0x1d}} , + {{0x61, 0x2e, 0xc2, 0xdd, 0x95, 0x57, 0xd1, 0xab, 0x80, 0xe8, 0x63, 0x17, 0xb5, 0x48, 0xe4, 0x8a, 0x11, 0x9e, 0x72, 0xbe, 0x85, 0x8d, 0x51, 0x0a, 0xf2, 0x9f, 0xe0, 0x1c, 0xa9, 0x07, 0x28, 0x7b}}}, +{{{0xbb, 0x71, 0x14, 0x5e, 0x26, 0x8c, 0x3d, 0xc8, 0xe9, 0x7c, 0xd3, 0xd6, 0xd1, 0x2f, 0x07, 0x6d, 0xe6, 0xdf, 0xfb, 0x79, 0xd6, 0x99, 0x59, 0x96, 0x48, 0x40, 0x0f, 0x3a, 0x7b, 0xb2, 0xa0, 0x72}} , + {{0x4e, 0x3b, 0x69, 0xc8, 0x43, 0x75, 0x51, 0x6c, 0x79, 0x56, 0xe4, 0xcb, 0xf7, 0xa6, 0x51, 0xc2, 0x2c, 0x42, 0x0b, 0xd4, 0x82, 0x20, 0x1c, 0x01, 0x08, 0x66, 0xd7, 0xbf, 0x04, 0x56, 0xfc, 0x02}}}, +{{{0x24, 0xe8, 0xb7, 0x60, 0xae, 0x47, 0x80, 0xfc, 0xe5, 0x23, 0xe7, 0xc2, 0xc9, 0x85, 0xe6, 0x98, 0xa0, 0x29, 0x4e, 0xe1, 0x84, 0x39, 0x2d, 0x95, 0x2c, 0xf3, 0x45, 0x3c, 0xff, 0xaf, 0x27, 0x4c}} , + {{0x6b, 0xa6, 0xf5, 0x4b, 0x11, 0xbd, 0xba, 0x5b, 0x9e, 0xc4, 0xa4, 0x51, 0x1e, 0xbe, 0xd0, 0x90, 0x3a, 0x9c, 0xc2, 0x26, 0xb6, 0x1e, 0xf1, 0x95, 0x7d, 0xc8, 0x6d, 0x52, 0xe6, 0x99, 0x2c, 0x5f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x85, 0xe0, 0x24, 0x32, 0xb4, 0xd1, 0xef, 0xfc, 0x69, 0xa2, 0xbf, 0x8f, 0x72, 0x2c, 0x95, 0xf6, 0xe4, 0x6e, 0x7d, 0x90, 0xf7, 0x57, 0x81, 0xa0, 0xf7, 0xda, 0xef, 0x33, 0x07, 0xe3, 0x6b, 0x78}} , + {{0x36, 0x27, 0x3e, 0xc6, 0x12, 0x07, 0xab, 0x4e, 0xbe, 0x69, 0x9d, 0xb3, 0xbe, 0x08, 0x7c, 0x2a, 0x47, 0x08, 0xfd, 0xd4, 0xcd, 0x0e, 0x27, 0x34, 0x5b, 0x98, 0x34, 0x2f, 0x77, 0x5f, 0x3a, 0x65}}}, +{{{0x13, 0xaa, 0x2e, 0x4c, 0xf0, 0x22, 0xb8, 0x6c, 0xb3, 0x19, 0x4d, 0xeb, 0x6b, 0xd0, 0xa4, 0xc6, 0x9c, 0xdd, 0xc8, 0x5b, 0x81, 0x57, 0x89, 0xdf, 0x33, 0xa9, 0x68, 0x49, 0x80, 0xe4, 0xfe, 0x21}} , + {{0x00, 0x17, 0x90, 0x30, 0xe9, 0xd3, 0x60, 0x30, 0x31, 0xc2, 0x72, 0x89, 0x7a, 0x36, 0xa5, 0xbd, 0x39, 0x83, 0x85, 0x50, 0xa1, 0x5d, 0x6c, 0x41, 0x1d, 0xb5, 0x2c, 0x07, 0x40, 0x77, 0x0b, 0x50}}}, +{{{0x64, 0x34, 0xec, 0xc0, 0x9e, 0x44, 0x41, 0xaf, 0xa0, 0x36, 0x05, 0x6d, 0xea, 0x30, 0x25, 0x46, 0x35, 0x24, 0x9d, 0x86, 0xbd, 0x95, 0xf1, 0x6a, 0x46, 0xd7, 0x94, 0x54, 0xf9, 0x3b, 0xbd, 0x5d}} , + {{0x77, 0x5b, 0xe2, 0x37, 0xc7, 0xe1, 0x7c, 0x13, 0x8c, 0x9f, 0x7b, 0x7b, 0x2a, 0xce, 0x42, 0xa3, 0xb9, 0x2a, 0x99, 0xa8, 0xc0, 0xd8, 0x3c, 0x86, 0xb0, 0xfb, 0xe9, 0x76, 0x77, 0xf7, 0xf5, 0x56}}}, +{{{0xdf, 0xb3, 0x46, 0x11, 0x6e, 0x13, 0xb7, 0x28, 0x4e, 0x56, 0xdd, 0xf1, 0xac, 0xad, 0x58, 0xc3, 0xf8, 0x88, 0x94, 0x5e, 0x06, 0x98, 0xa1, 0xe4, 0x6a, 0xfb, 0x0a, 0x49, 0x5d, 0x8a, 0xfe, 0x77}} , + {{0x46, 0x02, 0xf5, 0xa5, 0xaf, 0xc5, 0x75, 0x6d, 0xba, 0x45, 0x35, 0x0a, 0xfe, 0xc9, 0xac, 0x22, 0x91, 0x8d, 0x21, 0x95, 0x33, 0x03, 0xc0, 0x8a, 0x16, 0xf3, 0x39, 0xe0, 0x01, 0x0f, 0x53, 0x3c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x34, 0x75, 0x37, 0x1f, 0x34, 0x4e, 0xa9, 0x1d, 0x68, 0x67, 0xf8, 0x49, 0x98, 0x96, 0xfc, 0x4c, 0x65, 0x97, 0xf7, 0x02, 0x4a, 0x52, 0x6c, 0x01, 0xbd, 0x48, 0xbb, 0x1b, 0xed, 0xa4, 0xe2, 0x53}} , + {{0x59, 0xd5, 0x9b, 0x5a, 0xa2, 0x90, 0xd3, 0xb8, 0x37, 0x4c, 0x55, 0x82, 0x28, 0x08, 0x0f, 0x7f, 0xaa, 0x81, 0x65, 0xe0, 0x0c, 0x52, 0xc9, 0xa3, 0x32, 0x27, 0x64, 0xda, 0xfd, 0x34, 0x23, 0x5a}}}, +{{{0xb5, 0xb0, 0x0c, 0x4d, 0xb3, 0x7b, 0x23, 0xc8, 0x1f, 0x8a, 0x39, 0x66, 0xe6, 0xba, 0x4c, 0x10, 0x37, 0xca, 0x9c, 0x7c, 0x05, 0x9e, 0xff, 0xc0, 0xf8, 0x8e, 0xb1, 0x8f, 0x6f, 0x67, 0x18, 0x26}} , + {{0x4b, 0x41, 0x13, 0x54, 0x23, 0x1a, 0xa4, 0x4e, 0xa9, 0x8b, 0x1e, 0x4b, 0xfc, 0x15, 0x24, 0xbb, 0x7e, 0xcb, 0xb6, 0x1e, 0x1b, 0xf5, 0xf2, 0xc8, 0x56, 0xec, 0x32, 0xa2, 0x60, 0x5b, 0xa0, 0x2a}}}, +{{{0xa4, 0x29, 0x47, 0x86, 0x2e, 0x92, 0x4f, 0x11, 0x4f, 0xf3, 0xb2, 0x5c, 0xd5, 0x3e, 0xa6, 0xb9, 0xc8, 0xe2, 0x33, 0x11, 0x1f, 0x01, 0x8f, 0xb0, 0x9b, 0xc7, 0xa5, 0xff, 0x83, 0x0f, 0x1e, 0x28}} , + {{0x1d, 0x29, 0x7a, 0xa1, 0xec, 0x8e, 0xb5, 0xad, 0xea, 0x02, 0x68, 0x60, 0x74, 0x29, 0x1c, 0xa5, 0xcf, 0xc8, 0x3b, 0x7d, 0x8b, 0x2b, 0x7c, 0xad, 0xa4, 0x40, 0x17, 0x51, 0x59, 0x7c, 0x2e, 0x5d}}}, +{{{0x0a, 0x6c, 0x4f, 0xbc, 0x3e, 0x32, 0xe7, 0x4a, 0x1a, 0x13, 0xc1, 0x49, 0x38, 0xbf, 0xf7, 0xc2, 0xd3, 0x8f, 0x6b, 0xad, 0x52, 0xf7, 0xcf, 0xbc, 0x27, 0xcb, 0x40, 0x67, 0x76, 0xcd, 0x6d, 0x56}} , + {{0xe5, 0xb0, 0x27, 0xad, 0xbe, 0x9b, 0xf2, 0xb5, 0x63, 0xde, 0x3a, 0x23, 0x95, 0xb7, 0x0a, 0x7e, 0xf3, 0x9e, 0x45, 0x6f, 0x19, 0x39, 0x75, 0x8f, 0x39, 0x3d, 0x0f, 0xc0, 0x9f, 0xf1, 0xe9, 0x51}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x88, 0xaa, 0x14, 0x24, 0x86, 0x94, 0x11, 0x12, 0x3e, 0x1a, 0xb5, 0xcc, 0xbb, 0xe0, 0x9c, 0xd5, 0x9c, 0x6d, 0xba, 0x58, 0x72, 0x8d, 0xfb, 0x22, 0x7b, 0x9f, 0x7c, 0x94, 0x30, 0xb3, 0x51, 0x21}} , + {{0xf6, 0x74, 0x3d, 0xf2, 0xaf, 0xd0, 0x1e, 0x03, 0x7c, 0x23, 0x6b, 0xc9, 0xfc, 0x25, 0x70, 0x90, 0xdc, 0x9a, 0xa4, 0xfb, 0x49, 0xfc, 0x3d, 0x0a, 0x35, 0x38, 0x6f, 0xe4, 0x7e, 0x50, 0x01, 0x2a}}}, +{{{0xd6, 0xe3, 0x96, 0x61, 0x3a, 0xfd, 0xef, 0x9b, 0x1f, 0x90, 0xa4, 0x24, 0x14, 0x5b, 0xc8, 0xde, 0x50, 0xb1, 0x1d, 0xaf, 0xe8, 0x55, 0x8a, 0x87, 0x0d, 0xfe, 0xaa, 0x3b, 0x82, 0x2c, 0x8d, 0x7b}} , + {{0x85, 0x0c, 0xaf, 0xf8, 0x83, 0x44, 0x49, 0xd9, 0x45, 0xcf, 0xf7, 0x48, 0xd9, 0x53, 0xb4, 0xf1, 0x65, 0xa0, 0xe1, 0xc3, 0xb3, 0x15, 0xed, 0x89, 0x9b, 0x4f, 0x62, 0xb3, 0x57, 0xa5, 0x45, 0x1c}}}, +{{{0x8f, 0x12, 0xea, 0xaf, 0xd1, 0x1f, 0x79, 0x10, 0x0b, 0xf6, 0xa3, 0x7b, 0xea, 0xac, 0x8b, 0x57, 0x32, 0x62, 0xe7, 0x06, 0x12, 0x51, 0xa0, 0x3b, 0x43, 0x5e, 0xa4, 0x20, 0x78, 0x31, 0xce, 0x0d}} , + {{0x84, 0x7c, 0xc2, 0xa6, 0x91, 0x23, 0xce, 0xbd, 0xdc, 0xf9, 0xce, 0xd5, 0x75, 0x30, 0x22, 0xe6, 0xf9, 0x43, 0x62, 0x0d, 0xf7, 0x75, 0x9d, 0x7f, 0x8c, 0xff, 0x7d, 0xe4, 0x72, 0xac, 0x9f, 0x1c}}}, +{{{0x88, 0xc1, 0x99, 0xd0, 0x3c, 0x1c, 0x5d, 0xb4, 0xef, 0x13, 0x0f, 0x90, 0xb9, 0x36, 0x2f, 0x95, 0x95, 0xc6, 0xdc, 0xde, 0x0a, 0x51, 0xe2, 0x8d, 0xf3, 0xbc, 0x51, 0xec, 0xdf, 0xb1, 0xa2, 0x5f}} , + {{0x2e, 0x68, 0xa1, 0x23, 0x7d, 0x9b, 0x40, 0x69, 0x85, 0x7b, 0x42, 0xbf, 0x90, 0x4b, 0xd6, 0x40, 0x2f, 0xd7, 0x52, 0x52, 0xb2, 0x21, 0xde, 0x64, 0xbd, 0x88, 0xc3, 0x6d, 0xa5, 0xfa, 0x81, 0x3f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xfb, 0xfd, 0x47, 0x7b, 0x8a, 0x66, 0x9e, 0x79, 0x2e, 0x64, 0x82, 0xef, 0xf7, 0x21, 0xec, 0xf6, 0xd8, 0x86, 0x09, 0x31, 0x7c, 0xdd, 0x03, 0x6a, 0x58, 0xa0, 0x77, 0xb7, 0x9b, 0x8c, 0x87, 0x1f}} , + {{0x55, 0x47, 0xe4, 0xa8, 0x3d, 0x55, 0x21, 0x34, 0xab, 0x1d, 0xae, 0xe0, 0xf4, 0xea, 0xdb, 0xc5, 0xb9, 0x58, 0xbf, 0xc4, 0x2a, 0x89, 0x31, 0x1a, 0xf4, 0x2d, 0xe1, 0xca, 0x37, 0x99, 0x47, 0x59}}}, +{{{0xc7, 0xca, 0x63, 0xc1, 0x49, 0xa9, 0x35, 0x45, 0x55, 0x7e, 0xda, 0x64, 0x32, 0x07, 0x50, 0xf7, 0x32, 0xac, 0xde, 0x75, 0x58, 0x9b, 0x11, 0xb2, 0x3a, 0x1f, 0xf5, 0xf7, 0x79, 0x04, 0xe6, 0x08}} , + {{0x46, 0xfa, 0x22, 0x4b, 0xfa, 0xe1, 0xfe, 0x96, 0xfc, 0x67, 0xba, 0x67, 0x97, 0xc4, 0xe7, 0x1b, 0x86, 0x90, 0x5f, 0xee, 0xf4, 0x5b, 0x11, 0xb2, 0xcd, 0xad, 0xee, 0xc2, 0x48, 0x6c, 0x2b, 0x1b}}}, +{{{0xe3, 0x39, 0x62, 0xb4, 0x4f, 0x31, 0x04, 0xc9, 0xda, 0xd5, 0x73, 0x51, 0x57, 0xc5, 0xb8, 0xf3, 0xa3, 0x43, 0x70, 0xe4, 0x61, 0x81, 0x84, 0xe2, 0xbb, 0xbf, 0x4f, 0x9e, 0xa4, 0x5e, 0x74, 0x06}} , + {{0x29, 0xac, 0xff, 0x27, 0xe0, 0x59, 0xbe, 0x39, 0x9c, 0x0d, 0x83, 0xd7, 0x10, 0x0b, 0x15, 0xb7, 0xe1, 0xc2, 0x2c, 0x30, 0x73, 0x80, 0x3a, 0x7d, 0x5d, 0xab, 0x58, 0x6b, 0xc1, 0xf0, 0xf4, 0x22}}}, +{{{0xfe, 0x7f, 0xfb, 0x35, 0x7d, 0xc6, 0x01, 0x23, 0x28, 0xc4, 0x02, 0xac, 0x1f, 0x42, 0xb4, 0x9d, 0xfc, 0x00, 0x94, 0xa5, 0xee, 0xca, 0xda, 0x97, 0x09, 0x41, 0x77, 0x87, 0x5d, 0x7b, 0x87, 0x78}} , + {{0xf5, 0xfb, 0x90, 0x2d, 0x81, 0x19, 0x9e, 0x2f, 0x6d, 0x85, 0x88, 0x8c, 0x40, 0x5c, 0x77, 0x41, 0x4d, 0x01, 0x19, 0x76, 0x60, 0xe8, 0x4c, 0x48, 0xe4, 0x33, 0x83, 0x32, 0x6c, 0xb4, 0x41, 0x03}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xff, 0x10, 0xc2, 0x09, 0x4f, 0x6e, 0xf4, 0xd2, 0xdf, 0x7e, 0xca, 0x7b, 0x1c, 0x1d, 0xba, 0xa3, 0xb6, 0xda, 0x67, 0x33, 0xd4, 0x87, 0x36, 0x4b, 0x11, 0x20, 0x05, 0xa6, 0x29, 0xc1, 0x87, 0x17}} , + {{0xf6, 0x96, 0xca, 0x2f, 0xda, 0x38, 0xa7, 0x1b, 0xfc, 0xca, 0x7d, 0xfe, 0x08, 0x89, 0xe2, 0x47, 0x2b, 0x6a, 0x5d, 0x4b, 0xfa, 0xa1, 0xb4, 0xde, 0xb6, 0xc2, 0x31, 0x51, 0xf5, 0xe0, 0xa4, 0x0b}}}, +{{{0x5c, 0xe5, 0xc6, 0x04, 0x8e, 0x2b, 0x57, 0xbe, 0x38, 0x85, 0x23, 0xcb, 0xb7, 0xbe, 0x4f, 0xa9, 0xd3, 0x6e, 0x12, 0xaa, 0xd5, 0xb2, 0x2e, 0x93, 0x29, 0x9a, 0x4a, 0x88, 0x18, 0x43, 0xf5, 0x01}} , + {{0x50, 0xfc, 0xdb, 0xa2, 0x59, 0x21, 0x8d, 0xbd, 0x7e, 0x33, 0xae, 0x2f, 0x87, 0x1a, 0xd0, 0x97, 0xc7, 0x0d, 0x4d, 0x63, 0x01, 0xef, 0x05, 0x84, 0xec, 0x40, 0xdd, 0xa8, 0x0a, 0x4f, 0x70, 0x0b}}}, +{{{0x41, 0x69, 0x01, 0x67, 0x5c, 0xd3, 0x8a, 0xc5, 0xcf, 0x3f, 0xd1, 0x57, 0xd1, 0x67, 0x3e, 0x01, 0x39, 0xb5, 0xcb, 0x81, 0x56, 0x96, 0x26, 0xb6, 0xc2, 0xe7, 0x5c, 0xfb, 0x63, 0x97, 0x58, 0x06}} , + {{0x0c, 0x0e, 0xf3, 0xba, 0xf0, 0xe5, 0xba, 0xb2, 0x57, 0x77, 0xc6, 0x20, 0x9b, 0x89, 0x24, 0xbe, 0xf2, 0x9c, 0x8a, 0xba, 0x69, 0xc1, 0xf1, 0xb0, 0x4f, 0x2a, 0x05, 0x9a, 0xee, 0x10, 0x7e, 0x36}}}, +{{{0x3f, 0x26, 0xe9, 0x40, 0xe9, 0x03, 0xad, 0x06, 0x69, 0x91, 0xe0, 0xd1, 0x89, 0x60, 0x84, 0x79, 0xde, 0x27, 0x6d, 0xe6, 0x76, 0xbd, 0xea, 0xe6, 0xae, 0x48, 0xc3, 0x67, 0xc0, 0x57, 0xcd, 0x2f}} , + {{0x7f, 0xc1, 0xdc, 0xb9, 0xc7, 0xbc, 0x86, 0x3d, 0x55, 0x4b, 0x28, 0x7a, 0xfb, 0x4d, 0xc7, 0xf8, 0xbc, 0x67, 0x2a, 0x60, 0x4d, 0x8f, 0x07, 0x0b, 0x1a, 0x17, 0xbf, 0xfa, 0xac, 0xa7, 0x3d, 0x1a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x91, 0x3f, 0xed, 0x5e, 0x18, 0x78, 0x3f, 0x23, 0x2c, 0x0d, 0x8c, 0x44, 0x00, 0xe8, 0xfb, 0xe9, 0x8e, 0xd6, 0xd1, 0x36, 0x58, 0x57, 0x9e, 0xae, 0x4b, 0x5c, 0x0b, 0x07, 0xbc, 0x6b, 0x55, 0x2b}} , + {{0x6f, 0x4d, 0x17, 0xd7, 0xe1, 0x84, 0xd9, 0x78, 0xb1, 0x90, 0xfd, 0x2e, 0xb3, 0xb5, 0x19, 0x3f, 0x1b, 0xfa, 0xc0, 0x68, 0xb3, 0xdd, 0x00, 0x2e, 0x89, 0xbd, 0x7e, 0x80, 0x32, 0x13, 0xa0, 0x7b}}}, +{{{0x1a, 0x6f, 0x40, 0xaf, 0x44, 0x44, 0xb0, 0x43, 0x8f, 0x0d, 0xd0, 0x1e, 0xc4, 0x0b, 0x19, 0x5d, 0x8e, 0xfe, 0xc1, 0xf3, 0xc5, 0x5c, 0x91, 0xf8, 0x04, 0x4e, 0xbe, 0x90, 0xb4, 0x47, 0x5c, 0x3f}} , + {{0xb0, 0x3b, 0x2c, 0xf3, 0xfe, 0x32, 0x71, 0x07, 0x3f, 0xaa, 0xba, 0x45, 0x60, 0xa8, 0x8d, 0xea, 0x54, 0xcb, 0x39, 0x10, 0xb4, 0xf2, 0x8b, 0xd2, 0x14, 0x82, 0x42, 0x07, 0x8e, 0xe9, 0x7c, 0x53}}}, +{{{0xb0, 0xae, 0xc1, 0x8d, 0xc9, 0x8f, 0xb9, 0x7a, 0x77, 0xef, 0xba, 0x79, 0xa0, 0x3c, 0xa8, 0xf5, 0x6a, 0xe2, 0x3f, 0x5d, 0x00, 0xe3, 0x4b, 0x45, 0x24, 0x7b, 0x43, 0x78, 0x55, 0x1d, 0x2b, 0x1e}} , + {{0x01, 0xb8, 0xd6, 0x16, 0x67, 0xa0, 0x15, 0xb9, 0xe1, 0x58, 0xa4, 0xa7, 0x31, 0x37, 0x77, 0x2f, 0x8b, 0x12, 0x9f, 0xf4, 0x3f, 0xc7, 0x36, 0x66, 0xd2, 0xa8, 0x56, 0xf7, 0x7f, 0x74, 0xc6, 0x41}}}, +{{{0x5d, 0xf8, 0xb4, 0xa8, 0x30, 0xdd, 0xcc, 0x38, 0xa5, 0xd3, 0xca, 0xd8, 0xd1, 0xf8, 0xb2, 0x31, 0x91, 0xd4, 0x72, 0x05, 0x57, 0x4a, 0x3b, 0x82, 0x4a, 0xc6, 0x68, 0x20, 0xe2, 0x18, 0x41, 0x61}} , + {{0x19, 0xd4, 0x8d, 0x47, 0x29, 0x12, 0x65, 0xb0, 0x11, 0x78, 0x47, 0xb5, 0xcb, 0xa3, 0xa5, 0xfa, 0x05, 0x85, 0x54, 0xa9, 0x33, 0x97, 0x8d, 0x2b, 0xc2, 0xfe, 0x99, 0x35, 0x28, 0xe5, 0xeb, 0x63}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xb1, 0x3f, 0x3f, 0xef, 0xd8, 0xf4, 0xfc, 0xb3, 0xa0, 0x60, 0x50, 0x06, 0x2b, 0x29, 0x52, 0x70, 0x15, 0x0b, 0x24, 0x24, 0xf8, 0x5f, 0x79, 0x18, 0xcc, 0xff, 0x89, 0x99, 0x84, 0xa1, 0xae, 0x13}} , + {{0x44, 0x1f, 0xb8, 0xc2, 0x01, 0xc1, 0x30, 0x19, 0x55, 0x05, 0x60, 0x10, 0xa4, 0x6c, 0x2d, 0x67, 0x70, 0xe5, 0x25, 0x1b, 0xf2, 0xbf, 0xdd, 0xfb, 0x70, 0x2b, 0xa1, 0x8c, 0x9c, 0x94, 0x84, 0x08}}}, +{{{0xe7, 0xc4, 0x43, 0x4d, 0xc9, 0x2b, 0x69, 0x5d, 0x1d, 0x3c, 0xaf, 0xbb, 0x43, 0x38, 0x4e, 0x98, 0x3d, 0xed, 0x0d, 0x21, 0x03, 0xfd, 0xf0, 0x99, 0x47, 0x04, 0xb0, 0x98, 0x69, 0x55, 0x72, 0x0f}} , + {{0x5e, 0xdf, 0x15, 0x53, 0x3b, 0x86, 0x80, 0xb0, 0xf1, 0x70, 0x68, 0x8f, 0x66, 0x7c, 0x0e, 0x49, 0x1a, 0xd8, 0x6b, 0xfe, 0x4e, 0xef, 0xca, 0x47, 0xd4, 0x03, 0xc1, 0x37, 0x50, 0x9c, 0xc1, 0x16}}}, +{{{0xcd, 0x24, 0xc6, 0x3e, 0x0c, 0x82, 0x9b, 0x91, 0x2b, 0x61, 0x4a, 0xb2, 0x0f, 0x88, 0x55, 0x5f, 0x5a, 0x57, 0xff, 0xe5, 0x74, 0x0b, 0x13, 0x43, 0x00, 0xd8, 0x6b, 0xcf, 0xd2, 0x15, 0x03, 0x2c}} , + {{0xdc, 0xff, 0x15, 0x61, 0x2f, 0x4a, 0x2f, 0x62, 0xf2, 0x04, 0x2f, 0xb5, 0x0c, 0xb7, 0x1e, 0x3f, 0x74, 0x1a, 0x0f, 0xd7, 0xea, 0xcd, 0xd9, 0x7d, 0xf6, 0x12, 0x0e, 0x2f, 0xdb, 0x5a, 0x3b, 0x16}}}, +{{{0x1b, 0x37, 0x47, 0xe3, 0xf5, 0x9e, 0xea, 0x2c, 0x2a, 0xe7, 0x82, 0x36, 0xf4, 0x1f, 0x81, 0x47, 0x92, 0x4b, 0x69, 0x0e, 0x11, 0x8c, 0x5d, 0x53, 0x5b, 0x81, 0x27, 0x08, 0xbc, 0xa0, 0xae, 0x25}} , + {{0x69, 0x32, 0xa1, 0x05, 0x11, 0x42, 0x00, 0xd2, 0x59, 0xac, 0x4d, 0x62, 0x8b, 0x13, 0xe2, 0x50, 0x5d, 0xa0, 0x9d, 0x9b, 0xfd, 0xbb, 0x12, 0x41, 0x75, 0x41, 0x9e, 0xcc, 0xdc, 0xc7, 0xdc, 0x5d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd9, 0xe3, 0x38, 0x06, 0x46, 0x70, 0x82, 0x5e, 0x28, 0x49, 0x79, 0xff, 0x25, 0xd2, 0x4e, 0x29, 0x8d, 0x06, 0xb0, 0x23, 0xae, 0x9b, 0x66, 0xe4, 0x7d, 0xc0, 0x70, 0x91, 0xa3, 0xfc, 0xec, 0x4e}} , + {{0x62, 0x12, 0x37, 0x6a, 0x30, 0xf6, 0x1e, 0xfb, 0x14, 0x5c, 0x0d, 0x0e, 0xb7, 0x81, 0x6a, 0xe7, 0x08, 0x05, 0xac, 0xaa, 0x38, 0x46, 0xe2, 0x73, 0xea, 0x4b, 0x07, 0x81, 0x43, 0x7c, 0x9e, 0x5e}}}, +{{{0xfc, 0xf9, 0x21, 0x4f, 0x2e, 0x76, 0x9b, 0x1f, 0x28, 0x60, 0x77, 0x43, 0x32, 0x9d, 0xbe, 0x17, 0x30, 0x2a, 0xc6, 0x18, 0x92, 0x66, 0x62, 0x30, 0x98, 0x40, 0x11, 0xa6, 0x7f, 0x18, 0x84, 0x28}} , + {{0x3f, 0xab, 0xd3, 0xf4, 0x8a, 0x76, 0xa1, 0x3c, 0xca, 0x2d, 0x49, 0xc3, 0xea, 0x08, 0x0b, 0x85, 0x17, 0x2a, 0xc3, 0x6c, 0x08, 0xfd, 0x57, 0x9f, 0x3d, 0x5f, 0xdf, 0x67, 0x68, 0x42, 0x00, 0x32}}}, +{{{0x51, 0x60, 0x1b, 0x06, 0x4f, 0x8a, 0x21, 0xba, 0x38, 0xa8, 0xba, 0xd6, 0x40, 0xf6, 0xe9, 0x9b, 0x76, 0x4d, 0x56, 0x21, 0x5b, 0x0a, 0x9b, 0x2e, 0x4f, 0x3d, 0x81, 0x32, 0x08, 0x9f, 0x97, 0x5b}} , + {{0xe5, 0x44, 0xec, 0x06, 0x9d, 0x90, 0x79, 0x9f, 0xd3, 0xe0, 0x79, 0xaf, 0x8f, 0x10, 0xfd, 0xdd, 0x04, 0xae, 0x27, 0x97, 0x46, 0x33, 0x79, 0xea, 0xb8, 0x4e, 0xca, 0x5a, 0x59, 0x57, 0xe1, 0x0e}}}, +{{{0x1a, 0xda, 0xf3, 0xa5, 0x41, 0x43, 0x28, 0xfc, 0x7e, 0xe7, 0x71, 0xea, 0xc6, 0x3b, 0x59, 0xcc, 0x2e, 0xd3, 0x40, 0xec, 0xb3, 0x13, 0x6f, 0x44, 0xcd, 0x13, 0xb2, 0x37, 0xf2, 0x6e, 0xd9, 0x1c}} , + {{0xe3, 0xdb, 0x60, 0xcd, 0x5c, 0x4a, 0x18, 0x0f, 0xef, 0x73, 0x36, 0x71, 0x8c, 0xf6, 0x11, 0xb4, 0xd8, 0xce, 0x17, 0x5e, 0x4f, 0x26, 0x77, 0x97, 0x5f, 0xcb, 0xef, 0x91, 0xeb, 0x6a, 0x62, 0x7a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x18, 0x4a, 0xa2, 0x97, 0x08, 0x81, 0x2d, 0x83, 0xc4, 0xcc, 0xf0, 0x83, 0x7e, 0xec, 0x0d, 0x95, 0x4c, 0x5b, 0xfb, 0xfa, 0x98, 0x80, 0x4a, 0x66, 0x56, 0x0c, 0x51, 0xb3, 0xf2, 0x04, 0x5d, 0x27}} , + {{0x3b, 0xb9, 0xb8, 0x06, 0x5a, 0x2e, 0xfe, 0xc3, 0x82, 0x37, 0x9c, 0xa3, 0x11, 0x1f, 0x9c, 0xa6, 0xda, 0x63, 0x48, 0x9b, 0xad, 0xde, 0x2d, 0xa6, 0xbc, 0x6e, 0x32, 0xda, 0x27, 0x65, 0xdd, 0x57}}}, +{{{0x84, 0x4f, 0x37, 0x31, 0x7d, 0x2e, 0xbc, 0xad, 0x87, 0x07, 0x2a, 0x6b, 0x37, 0xfc, 0x5f, 0xeb, 0x4e, 0x75, 0x35, 0xa6, 0xde, 0xab, 0x0a, 0x19, 0x3a, 0xb7, 0xb1, 0xef, 0x92, 0x6a, 0x3b, 0x3c}} , + {{0x3b, 0xb2, 0x94, 0x6d, 0x39, 0x60, 0xac, 0xee, 0xe7, 0x81, 0x1a, 0x3b, 0x76, 0x87, 0x5c, 0x05, 0x94, 0x2a, 0x45, 0xb9, 0x80, 0xe9, 0x22, 0xb1, 0x07, 0xcb, 0x40, 0x9e, 0x70, 0x49, 0x6d, 0x12}}}, +{{{0xfd, 0x18, 0x78, 0x84, 0xa8, 0x4c, 0x7d, 0x6e, 0x59, 0xa6, 0xe5, 0x74, 0xf1, 0x19, 0xa6, 0x84, 0x2e, 0x51, 0xc1, 0x29, 0x13, 0xf2, 0x14, 0x6b, 0x5d, 0x53, 0x51, 0xf7, 0xef, 0xbf, 0x01, 0x22}} , + {{0xa4, 0x4b, 0x62, 0x4c, 0xe6, 0xfd, 0x72, 0x07, 0xf2, 0x81, 0xfc, 0xf2, 0xbd, 0x12, 0x7c, 0x68, 0x76, 0x2a, 0xba, 0xf5, 0x65, 0xb1, 0x1f, 0x17, 0x0a, 0x38, 0xb0, 0xbf, 0xc0, 0xf8, 0xf4, 0x2a}}}, +{{{0x55, 0x60, 0x55, 0x5b, 0xe4, 0x1d, 0x71, 0x4c, 0x9d, 0x5b, 0x9f, 0x70, 0xa6, 0x85, 0x9a, 0x2c, 0xa0, 0xe2, 0x32, 0x48, 0xce, 0x9e, 0x2a, 0xa5, 0x07, 0x3b, 0xc7, 0x6c, 0x86, 0x77, 0xde, 0x3c}} , + {{0xf7, 0x18, 0x7a, 0x96, 0x7e, 0x43, 0x57, 0xa9, 0x55, 0xfc, 0x4e, 0xb6, 0x72, 0x00, 0xf2, 0xe4, 0xd7, 0x52, 0xd3, 0xd3, 0xb6, 0x85, 0xf6, 0x71, 0xc7, 0x44, 0x3f, 0x7f, 0xd7, 0xb3, 0xf2, 0x79}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x46, 0xca, 0xa7, 0x55, 0x7b, 0x79, 0xf3, 0xca, 0x5a, 0x65, 0xf6, 0xed, 0x50, 0x14, 0x7b, 0xe4, 0xc4, 0x2a, 0x65, 0x9e, 0xe2, 0xf9, 0xca, 0xa7, 0x22, 0x26, 0x53, 0xcb, 0x21, 0x5b, 0xa7, 0x31}} , + {{0x90, 0xd7, 0xc5, 0x26, 0x08, 0xbd, 0xb0, 0x53, 0x63, 0x58, 0xc3, 0x31, 0x5e, 0x75, 0x46, 0x15, 0x91, 0xa6, 0xf8, 0x2f, 0x1a, 0x08, 0x65, 0x88, 0x2f, 0x98, 0x04, 0xf1, 0x7c, 0x6e, 0x00, 0x77}}}, +{{{0x81, 0x21, 0x61, 0x09, 0xf6, 0x4e, 0xf1, 0x92, 0xee, 0x63, 0x61, 0x73, 0x87, 0xc7, 0x54, 0x0e, 0x42, 0x4b, 0xc9, 0x47, 0xd1, 0xb8, 0x7e, 0x91, 0x75, 0x37, 0x99, 0x28, 0xb8, 0xdd, 0x7f, 0x50}} , + {{0x89, 0x8f, 0xc0, 0xbe, 0x5d, 0xd6, 0x9f, 0xa0, 0xf0, 0x9d, 0x81, 0xce, 0x3a, 0x7b, 0x98, 0x58, 0xbb, 0xd7, 0x78, 0xc8, 0x3f, 0x13, 0xf1, 0x74, 0x19, 0xdf, 0xf8, 0x98, 0x89, 0x5d, 0xfa, 0x5f}}}, +{{{0x9e, 0x35, 0x85, 0x94, 0x47, 0x1f, 0x90, 0x15, 0x26, 0xd0, 0x84, 0xed, 0x8a, 0x80, 0xf7, 0x63, 0x42, 0x86, 0x27, 0xd7, 0xf4, 0x75, 0x58, 0xdc, 0x9c, 0xc0, 0x22, 0x7e, 0x20, 0x35, 0xfd, 0x1f}} , + {{0x68, 0x0e, 0x6f, 0x97, 0xba, 0x70, 0xbb, 0xa3, 0x0e, 0xe5, 0x0b, 0x12, 0xf4, 0xa2, 0xdc, 0x47, 0xf8, 0xe6, 0xd0, 0x23, 0x6c, 0x33, 0xa8, 0x99, 0x46, 0x6e, 0x0f, 0x44, 0xba, 0x76, 0x48, 0x0f}}}, +{{{0xa3, 0x2a, 0x61, 0x37, 0xe2, 0x59, 0x12, 0x0e, 0x27, 0xba, 0x64, 0x43, 0xae, 0xc0, 0x42, 0x69, 0x79, 0xa4, 0x1e, 0x29, 0x8b, 0x15, 0xeb, 0xf8, 0xaf, 0xd4, 0xa2, 0x68, 0x33, 0xb5, 0x7a, 0x24}} , + {{0x2c, 0x19, 0x33, 0xdd, 0x1b, 0xab, 0xec, 0x01, 0xb0, 0x23, 0xf8, 0x42, 0x2b, 0x06, 0x88, 0xea, 0x3d, 0x2d, 0x00, 0x2a, 0x78, 0x45, 0x4d, 0x38, 0xed, 0x2e, 0x2e, 0x44, 0x49, 0xed, 0xcb, 0x33}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xa0, 0x68, 0xe8, 0x41, 0x8f, 0x91, 0xf8, 0x11, 0x13, 0x90, 0x2e, 0xa7, 0xab, 0x30, 0xef, 0xad, 0xa0, 0x61, 0x00, 0x88, 0xef, 0xdb, 0xce, 0x5b, 0x5c, 0xbb, 0x62, 0xc8, 0x56, 0xf9, 0x00, 0x73}} , + {{0x3f, 0x60, 0xc1, 0x82, 0x2d, 0xa3, 0x28, 0x58, 0x24, 0x9e, 0x9f, 0xe3, 0x70, 0xcc, 0x09, 0x4e, 0x1a, 0x3f, 0x11, 0x11, 0x15, 0x07, 0x3c, 0xa4, 0x41, 0xe0, 0x65, 0xa3, 0x0a, 0x41, 0x6d, 0x11}}}, +{{{0x31, 0x40, 0x01, 0x52, 0x56, 0x94, 0x5b, 0x28, 0x8a, 0xaa, 0x52, 0xee, 0xd8, 0x0a, 0x05, 0x8d, 0xcd, 0xb5, 0xaa, 0x2e, 0x38, 0xaa, 0xb7, 0x87, 0xf7, 0x2b, 0xfb, 0x04, 0xcb, 0x84, 0x3d, 0x54}} , + {{0x20, 0xef, 0x59, 0xde, 0xa4, 0x2b, 0x93, 0x6e, 0x2e, 0xec, 0x42, 0x9a, 0xd4, 0x2d, 0xf4, 0x46, 0x58, 0x27, 0x2b, 0x18, 0x8f, 0x83, 0x3d, 0x69, 0x9e, 0xd4, 0x3e, 0xb6, 0xc5, 0xfd, 0x58, 0x03}}}, +{{{0x33, 0x89, 0xc9, 0x63, 0x62, 0x1c, 0x17, 0xb4, 0x60, 0xc4, 0x26, 0x68, 0x09, 0xc3, 0x2e, 0x37, 0x0f, 0x7b, 0xb4, 0x9c, 0xb6, 0xf9, 0xfb, 0xd4, 0x51, 0x78, 0xc8, 0x63, 0xea, 0x77, 0x47, 0x07}} , + {{0x32, 0xb4, 0x18, 0x47, 0x79, 0xcb, 0xd4, 0x5a, 0x07, 0x14, 0x0f, 0xa0, 0xd5, 0xac, 0xd0, 0x41, 0x40, 0xab, 0x61, 0x23, 0xe5, 0x2a, 0x2a, 0x6f, 0xf7, 0xa8, 0xd4, 0x76, 0xef, 0xe7, 0x45, 0x6c}}}, +{{{0xa1, 0x5e, 0x60, 0x4f, 0xfb, 0xe1, 0x70, 0x6a, 0x1f, 0x55, 0x4f, 0x09, 0xb4, 0x95, 0x33, 0x36, 0xc6, 0x81, 0x01, 0x18, 0x06, 0x25, 0x27, 0xa4, 0xb4, 0x24, 0xa4, 0x86, 0x03, 0x4c, 0xac, 0x02}} , + {{0x77, 0x38, 0xde, 0xd7, 0x60, 0x48, 0x07, 0xf0, 0x74, 0xa8, 0xff, 0x54, 0xe5, 0x30, 0x43, 0xff, 0x77, 0xfb, 0x21, 0x07, 0xff, 0xb2, 0x07, 0x6b, 0xe4, 0xe5, 0x30, 0xfc, 0x19, 0x6c, 0xa3, 0x01}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x13, 0xc5, 0x2c, 0xac, 0xd3, 0x83, 0x82, 0x7c, 0x29, 0xf7, 0x05, 0xa5, 0x00, 0xb6, 0x1f, 0x86, 0x55, 0xf4, 0xd6, 0x2f, 0x0c, 0x99, 0xd0, 0x65, 0x9b, 0x6b, 0x46, 0x0d, 0x43, 0xf8, 0x16, 0x28}} , + {{0x1e, 0x7f, 0xb4, 0x74, 0x7e, 0xb1, 0x89, 0x4f, 0x18, 0x5a, 0xab, 0x64, 0x06, 0xdf, 0x45, 0x87, 0xe0, 0x6a, 0xc6, 0xf0, 0x0e, 0xc9, 0x24, 0x35, 0x38, 0xea, 0x30, 0x54, 0xb4, 0xc4, 0x52, 0x54}}}, +{{{0xe9, 0x9f, 0xdc, 0x3f, 0xc1, 0x89, 0x44, 0x74, 0x27, 0xe4, 0xc1, 0x90, 0xff, 0x4a, 0xa7, 0x3c, 0xee, 0xcd, 0xf4, 0x1d, 0x25, 0x94, 0x7f, 0x63, 0x16, 0x48, 0xbc, 0x64, 0xfe, 0x95, 0xc4, 0x0c}} , + {{0x8b, 0x19, 0x75, 0x6e, 0x03, 0x06, 0x5e, 0x6a, 0x6f, 0x1a, 0x8c, 0xe3, 0xd3, 0x28, 0xf2, 0xe0, 0xb9, 0x7a, 0x43, 0x69, 0xe6, 0xd3, 0xc0, 0xfe, 0x7e, 0x97, 0xab, 0x6c, 0x7b, 0x8e, 0x13, 0x42}}}, +{{{0xd4, 0xca, 0x70, 0x3d, 0xab, 0xfb, 0x5f, 0x5e, 0x00, 0x0c, 0xcc, 0x77, 0x22, 0xf8, 0x78, 0x55, 0xae, 0x62, 0x35, 0xfb, 0x9a, 0xc6, 0x03, 0xe4, 0x0c, 0xee, 0xab, 0xc7, 0xc0, 0x89, 0x87, 0x54}} , + {{0x32, 0xad, 0xae, 0x85, 0x58, 0x43, 0xb8, 0xb1, 0xe6, 0x3e, 0x00, 0x9c, 0x78, 0x88, 0x56, 0xdb, 0x9c, 0xfc, 0x79, 0xf6, 0xf9, 0x41, 0x5f, 0xb7, 0xbc, 0x11, 0xf9, 0x20, 0x36, 0x1c, 0x53, 0x2b}}}, +{{{0x5a, 0x20, 0x5b, 0xa1, 0xa5, 0x44, 0x91, 0x24, 0x02, 0x63, 0x12, 0x64, 0xb8, 0x55, 0xf6, 0xde, 0x2c, 0xdb, 0x47, 0xb8, 0xc6, 0x0a, 0xc3, 0x00, 0x78, 0x93, 0xd8, 0xf5, 0xf5, 0x18, 0x28, 0x0a}} , + {{0xd6, 0x1b, 0x9a, 0x6c, 0xe5, 0x46, 0xea, 0x70, 0x96, 0x8d, 0x4e, 0x2a, 0x52, 0x21, 0x26, 0x4b, 0xb1, 0xbb, 0x0f, 0x7c, 0xa9, 0x9b, 0x04, 0xbb, 0x51, 0x08, 0xf1, 0x9a, 0xa4, 0x76, 0x7c, 0x18}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xfa, 0x94, 0xf7, 0x40, 0xd0, 0xd7, 0xeb, 0xa9, 0x82, 0x36, 0xd5, 0x15, 0xb9, 0x33, 0x7a, 0xbf, 0x8a, 0xf2, 0x63, 0xaa, 0x37, 0xf5, 0x59, 0xac, 0xbd, 0xbb, 0x32, 0x36, 0xbe, 0x73, 0x99, 0x38}} , + {{0x2c, 0xb3, 0xda, 0x7a, 0xd8, 0x3d, 0x99, 0xca, 0xd2, 0xf4, 0xda, 0x99, 0x8e, 0x4f, 0x98, 0xb7, 0xf4, 0xae, 0x3e, 0x9f, 0x8e, 0x35, 0x60, 0xa4, 0x33, 0x75, 0xa4, 0x04, 0x93, 0xb1, 0x6b, 0x4d}}}, +{{{0x97, 0x9d, 0xa8, 0xcd, 0x97, 0x7b, 0x9d, 0xb9, 0xe7, 0xa5, 0xef, 0xfd, 0xa8, 0x42, 0x6b, 0xc3, 0x62, 0x64, 0x7d, 0xa5, 0x1b, 0xc9, 0x9e, 0xd2, 0x45, 0xb9, 0xee, 0x03, 0xb0, 0xbf, 0xc0, 0x68}} , + {{0xed, 0xb7, 0x84, 0x2c, 0xf6, 0xd3, 0xa1, 0x6b, 0x24, 0x6d, 0x87, 0x56, 0x97, 0x59, 0x79, 0x62, 0x9f, 0xac, 0xed, 0xf3, 0xc9, 0x89, 0x21, 0x2e, 0x04, 0xb3, 0xcc, 0x2f, 0xbe, 0xd6, 0x0a, 0x4b}}}, +{{{0x39, 0x61, 0x05, 0xed, 0x25, 0x89, 0x8b, 0x5d, 0x1b, 0xcb, 0x0c, 0x55, 0xf4, 0x6a, 0x00, 0x8a, 0x46, 0xe8, 0x1e, 0xc6, 0x83, 0xc8, 0x5a, 0x76, 0xdb, 0xcc, 0x19, 0x7a, 0xcc, 0x67, 0x46, 0x0b}} , + {{0x53, 0xcf, 0xc2, 0xa1, 0xad, 0x6a, 0xf3, 0xcd, 0x8f, 0xc9, 0xde, 0x1c, 0xf8, 0x6c, 0x8f, 0xf8, 0x76, 0x42, 0xe7, 0xfe, 0xb2, 0x72, 0x21, 0x0a, 0x66, 0x74, 0x8f, 0xb7, 0xeb, 0xe4, 0x6f, 0x01}}}, +{{{0x22, 0x8c, 0x6b, 0xbe, 0xfc, 0x4d, 0x70, 0x62, 0x6e, 0x52, 0x77, 0x99, 0x88, 0x7e, 0x7b, 0x57, 0x7a, 0x0d, 0xfe, 0xdc, 0x72, 0x92, 0xf1, 0x68, 0x1d, 0x97, 0xd7, 0x7c, 0x8d, 0x53, 0x10, 0x37}} , + {{0x53, 0x88, 0x77, 0x02, 0xca, 0x27, 0xa8, 0xe5, 0x45, 0xe2, 0xa8, 0x48, 0x2a, 0xab, 0x18, 0xca, 0xea, 0x2d, 0x2a, 0x54, 0x17, 0x37, 0x32, 0x09, 0xdc, 0xe0, 0x4a, 0xb7, 0x7d, 0x82, 0x10, 0x7d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x8a, 0x64, 0x1e, 0x14, 0x0a, 0x57, 0xd4, 0xda, 0x5c, 0x96, 0x9b, 0x01, 0x4c, 0x67, 0xbf, 0x8b, 0x30, 0xfe, 0x08, 0xdb, 0x0d, 0xd5, 0xa8, 0xd7, 0x09, 0x11, 0x85, 0xa2, 0xd3, 0x45, 0xfb, 0x7e}} , + {{0xda, 0x8c, 0xc2, 0xd0, 0xac, 0x18, 0xe8, 0x52, 0x36, 0xd4, 0x21, 0xa3, 0xdd, 0x57, 0x22, 0x79, 0xb7, 0xf8, 0x71, 0x9d, 0xc6, 0x91, 0x70, 0x86, 0x56, 0xbf, 0xa1, 0x11, 0x8b, 0x19, 0xe1, 0x0f}}}, +{{{0x18, 0x32, 0x98, 0x2c, 0x8f, 0x91, 0xae, 0x12, 0xf0, 0x8c, 0xea, 0xf3, 0x3c, 0xb9, 0x5d, 0xe4, 0x69, 0xed, 0xb2, 0x47, 0x18, 0xbd, 0xce, 0x16, 0x52, 0x5c, 0x23, 0xe2, 0xa5, 0x25, 0x52, 0x5d}} , + {{0xb9, 0xb1, 0xe7, 0x5d, 0x4e, 0xbc, 0xee, 0xbb, 0x40, 0x81, 0x77, 0x82, 0x19, 0xab, 0xb5, 0xc6, 0xee, 0xab, 0x5b, 0x6b, 0x63, 0x92, 0x8a, 0x34, 0x8d, 0xcd, 0xee, 0x4f, 0x49, 0xe5, 0xc9, 0x7e}}}, +{{{0x21, 0xac, 0x8b, 0x22, 0xcd, 0xc3, 0x9a, 0xe9, 0x5e, 0x78, 0xbd, 0xde, 0xba, 0xad, 0xab, 0xbf, 0x75, 0x41, 0x09, 0xc5, 0x58, 0xa4, 0x7d, 0x92, 0xb0, 0x7f, 0xf2, 0xa1, 0xd1, 0xc0, 0xb3, 0x6d}} , + {{0x62, 0x4f, 0xd0, 0x75, 0x77, 0xba, 0x76, 0x77, 0xd7, 0xb8, 0xd8, 0x92, 0x6f, 0x98, 0x34, 0x3d, 0xd6, 0x4e, 0x1c, 0x0f, 0xf0, 0x8f, 0x2e, 0xf1, 0xb3, 0xbd, 0xb1, 0xb9, 0xec, 0x99, 0xb4, 0x07}}}, +{{{0x60, 0x57, 0x2e, 0x9a, 0x72, 0x1d, 0x6b, 0x6e, 0x58, 0x33, 0x24, 0x8c, 0x48, 0x39, 0x46, 0x8e, 0x89, 0x6a, 0x88, 0x51, 0x23, 0x62, 0xb5, 0x32, 0x09, 0x36, 0xe3, 0x57, 0xf5, 0x98, 0xde, 0x6f}} , + {{0x8b, 0x2c, 0x00, 0x48, 0x4a, 0xf9, 0x5b, 0x87, 0x69, 0x52, 0xe5, 0x5b, 0xd1, 0xb1, 0xe5, 0x25, 0x25, 0xe0, 0x9c, 0xc2, 0x13, 0x44, 0xe8, 0xb9, 0x0a, 0x70, 0xad, 0xbd, 0x0f, 0x51, 0x94, 0x69}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xa2, 0xdc, 0xab, 0xa9, 0x25, 0x2d, 0xac, 0x5f, 0x03, 0x33, 0x08, 0xe7, 0x7e, 0xfe, 0x95, 0x36, 0x3c, 0x5b, 0x3a, 0xd3, 0x05, 0x82, 0x1c, 0x95, 0x2d, 0xd8, 0x77, 0x7e, 0x02, 0xd9, 0x5b, 0x70}} , + {{0xc2, 0xfe, 0x1b, 0x0c, 0x67, 0xcd, 0xd6, 0xe0, 0x51, 0x8e, 0x2c, 0xe0, 0x79, 0x88, 0xf0, 0xcf, 0x41, 0x4a, 0xad, 0x23, 0xd4, 0x46, 0xca, 0x94, 0xa1, 0xc3, 0xeb, 0x28, 0x06, 0xfa, 0x17, 0x14}}}, +{{{0x7b, 0xaa, 0x70, 0x0a, 0x4b, 0xfb, 0xf5, 0xbf, 0x80, 0xc5, 0xcf, 0x08, 0x7a, 0xdd, 0xa1, 0xf4, 0x9d, 0x54, 0x50, 0x53, 0x23, 0x77, 0x23, 0xf5, 0x34, 0xa5, 0x22, 0xd1, 0x0d, 0x96, 0x2e, 0x47}} , + {{0xcc, 0xb7, 0x32, 0x89, 0x57, 0xd0, 0x98, 0x75, 0xe4, 0x37, 0x99, 0xa9, 0xe8, 0xba, 0xed, 0xba, 0xeb, 0xc7, 0x4f, 0x15, 0x76, 0x07, 0x0c, 0x4c, 0xef, 0x9f, 0x52, 0xfc, 0x04, 0x5d, 0x58, 0x10}}}, +{{{0xce, 0x82, 0xf0, 0x8f, 0x79, 0x02, 0xa8, 0xd1, 0xda, 0x14, 0x09, 0x48, 0xee, 0x8a, 0x40, 0x98, 0x76, 0x60, 0x54, 0x5a, 0xde, 0x03, 0x24, 0xf5, 0xe6, 0x2f, 0xe1, 0x03, 0xbf, 0x68, 0x82, 0x7f}} , + {{0x64, 0xe9, 0x28, 0xc7, 0xa4, 0xcf, 0x2a, 0xf9, 0x90, 0x64, 0x72, 0x2c, 0x8b, 0xeb, 0xec, 0xa0, 0xf2, 0x7d, 0x35, 0xb5, 0x90, 0x4d, 0x7f, 0x5b, 0x4a, 0x49, 0xe4, 0xb8, 0x3b, 0xc8, 0xa1, 0x2f}}}, +{{{0x8b, 0xc5, 0xcc, 0x3d, 0x69, 0xa6, 0xa1, 0x18, 0x44, 0xbc, 0x4d, 0x77, 0x37, 0xc7, 0x86, 0xec, 0x0c, 0xc9, 0xd6, 0x44, 0xa9, 0x23, 0x27, 0xb9, 0x03, 0x34, 0xa7, 0x0a, 0xd5, 0xc7, 0x34, 0x37}} , + {{0xf9, 0x7e, 0x3e, 0x66, 0xee, 0xf9, 0x99, 0x28, 0xff, 0xad, 0x11, 0xd8, 0xe2, 0x66, 0xc5, 0xcd, 0x0f, 0x0d, 0x0b, 0x6a, 0xfc, 0x7c, 0x24, 0xa8, 0x4f, 0xa8, 0x5e, 0x80, 0x45, 0x8b, 0x6c, 0x41}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xef, 0x1e, 0xec, 0xf7, 0x8d, 0x77, 0xf2, 0xea, 0xdb, 0x60, 0x03, 0x21, 0xc0, 0xff, 0x5e, 0x67, 0xc3, 0x71, 0x0b, 0x21, 0xb4, 0x41, 0xa0, 0x68, 0x38, 0xc6, 0x01, 0xa3, 0xd3, 0x51, 0x3c, 0x3c}} , + {{0x92, 0xf8, 0xd6, 0x4b, 0xef, 0x42, 0x13, 0xb2, 0x4a, 0xc4, 0x2e, 0x72, 0x3f, 0xc9, 0x11, 0xbd, 0x74, 0x02, 0x0e, 0xf5, 0x13, 0x9d, 0x83, 0x1a, 0x1b, 0xd5, 0x54, 0xde, 0xc4, 0x1e, 0x16, 0x6c}}}, +{{{0x27, 0x52, 0xe4, 0x63, 0xaa, 0x94, 0xe6, 0xc3, 0x28, 0x9c, 0xc6, 0x56, 0xac, 0xfa, 0xb6, 0xbd, 0xe2, 0xcc, 0x76, 0xc6, 0x27, 0x27, 0xa2, 0x8e, 0x78, 0x2b, 0x84, 0x72, 0x10, 0xbd, 0x4e, 0x2a}} , + {{0xea, 0xa7, 0x23, 0xef, 0x04, 0x61, 0x80, 0x50, 0xc9, 0x6e, 0xa5, 0x96, 0xd1, 0xd1, 0xc8, 0xc3, 0x18, 0xd7, 0x2d, 0xfd, 0x26, 0xbd, 0xcb, 0x7b, 0x92, 0x51, 0x0e, 0x4a, 0x65, 0x57, 0xb8, 0x49}}}, +{{{0xab, 0x55, 0x36, 0xc3, 0xec, 0x63, 0x55, 0x11, 0x55, 0xf6, 0xa5, 0xc7, 0x01, 0x5f, 0xfe, 0x79, 0xd8, 0x0a, 0xf7, 0x03, 0xd8, 0x98, 0x99, 0xf5, 0xd0, 0x00, 0x54, 0x6b, 0x66, 0x28, 0xf5, 0x25}} , + {{0x7a, 0x8d, 0xa1, 0x5d, 0x70, 0x5d, 0x51, 0x27, 0xee, 0x30, 0x65, 0x56, 0x95, 0x46, 0xde, 0xbd, 0x03, 0x75, 0xb4, 0x57, 0x59, 0x89, 0xeb, 0x02, 0x9e, 0xcc, 0x89, 0x19, 0xa7, 0xcb, 0x17, 0x67}}}, +{{{0x6a, 0xeb, 0xfc, 0x9a, 0x9a, 0x10, 0xce, 0xdb, 0x3a, 0x1c, 0x3c, 0x6a, 0x9d, 0xea, 0x46, 0xbc, 0x45, 0x49, 0xac, 0xe3, 0x41, 0x12, 0x7c, 0xf0, 0xf7, 0x4f, 0xf9, 0xf7, 0xff, 0x2c, 0x89, 0x04}} , + {{0x30, 0x31, 0x54, 0x1a, 0x46, 0xca, 0xe6, 0xc6, 0xcb, 0xe2, 0xc3, 0xc1, 0x8b, 0x75, 0x81, 0xbe, 0xee, 0xf8, 0xa3, 0x11, 0x1c, 0x25, 0xa3, 0xa7, 0x35, 0x51, 0x55, 0xe2, 0x25, 0xaa, 0xe2, 0x3a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xb4, 0x48, 0x10, 0x9f, 0x8a, 0x09, 0x76, 0xfa, 0xf0, 0x7a, 0xb0, 0x70, 0xf7, 0x83, 0x80, 0x52, 0x84, 0x2b, 0x26, 0xa2, 0xc4, 0x5d, 0x4f, 0xba, 0xb1, 0xc8, 0x40, 0x0d, 0x78, 0x97, 0xc4, 0x60}} , + {{0xd4, 0xb1, 0x6c, 0x08, 0xc7, 0x40, 0x38, 0x73, 0x5f, 0x0b, 0xf3, 0x76, 0x5d, 0xb2, 0xa5, 0x2f, 0x57, 0x57, 0x07, 0xed, 0x08, 0xa2, 0x6c, 0x4f, 0x08, 0x02, 0xb5, 0x0e, 0xee, 0x44, 0xfa, 0x22}}}, +{{{0x0f, 0x00, 0x3f, 0xa6, 0x04, 0x19, 0x56, 0x65, 0x31, 0x7f, 0x8b, 0xeb, 0x0d, 0xe1, 0x47, 0x89, 0x97, 0x16, 0x53, 0xfa, 0x81, 0xa7, 0xaa, 0xb2, 0xbf, 0x67, 0xeb, 0x72, 0x60, 0x81, 0x0d, 0x48}} , + {{0x7e, 0x13, 0x33, 0xcd, 0xa8, 0x84, 0x56, 0x1e, 0x67, 0xaf, 0x6b, 0x43, 0xac, 0x17, 0xaf, 0x16, 0xc0, 0x52, 0x99, 0x49, 0x5b, 0x87, 0x73, 0x7e, 0xb5, 0x43, 0xda, 0x6b, 0x1d, 0x0f, 0x2d, 0x55}}}, +{{{0xe9, 0x58, 0x1f, 0xff, 0x84, 0x3f, 0x93, 0x1c, 0xcb, 0xe1, 0x30, 0x69, 0xa5, 0x75, 0x19, 0x7e, 0x14, 0x5f, 0xf8, 0xfc, 0x09, 0xdd, 0xa8, 0x78, 0x9d, 0xca, 0x59, 0x8b, 0xd1, 0x30, 0x01, 0x13}} , + {{0xff, 0x76, 0x03, 0xc5, 0x4b, 0x89, 0x99, 0x70, 0x00, 0x59, 0x70, 0x9c, 0xd5, 0xd9, 0x11, 0x89, 0x5a, 0x46, 0xfe, 0xef, 0xdc, 0xd9, 0x55, 0x2b, 0x45, 0xa7, 0xb0, 0x2d, 0xfb, 0x24, 0xc2, 0x29}}}, +{{{0x38, 0x06, 0xf8, 0x0b, 0xac, 0x82, 0xc4, 0x97, 0x2b, 0x90, 0xe0, 0xf7, 0xa8, 0xab, 0x6c, 0x08, 0x80, 0x66, 0x90, 0x46, 0xf7, 0x26, 0x2d, 0xf8, 0xf1, 0xc4, 0x6b, 0x4a, 0x82, 0x98, 0x8e, 0x37}} , + {{0x8e, 0xb4, 0xee, 0xb8, 0xd4, 0x3f, 0xb2, 0x1b, 0xe0, 0x0a, 0x3d, 0x75, 0x34, 0x28, 0xa2, 0x8e, 0xc4, 0x92, 0x7b, 0xfe, 0x60, 0x6e, 0x6d, 0xb8, 0x31, 0x1d, 0x62, 0x0d, 0x78, 0x14, 0x42, 0x11}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x5e, 0xa8, 0xd8, 0x04, 0x9b, 0x73, 0xc9, 0xc9, 0xdc, 0x0d, 0x73, 0xbf, 0x0a, 0x0a, 0x73, 0xff, 0x18, 0x1f, 0x9c, 0x51, 0xaa, 0xc6, 0xf1, 0x83, 0x25, 0xfd, 0xab, 0xa3, 0x11, 0xd3, 0x01, 0x24}} , + {{0x4d, 0xe3, 0x7e, 0x38, 0x62, 0x5e, 0x64, 0xbb, 0x2b, 0x53, 0xb5, 0x03, 0x68, 0xc4, 0xf2, 0x2b, 0x5a, 0x03, 0x32, 0x99, 0x4a, 0x41, 0x9a, 0xe1, 0x1a, 0xae, 0x8c, 0x48, 0xf3, 0x24, 0x32, 0x65}}}, +{{{0xe8, 0xdd, 0xad, 0x3a, 0x8c, 0xea, 0xf4, 0xb3, 0xb2, 0xe5, 0x73, 0xf2, 0xed, 0x8b, 0xbf, 0xed, 0xb1, 0x0c, 0x0c, 0xfb, 0x2b, 0xf1, 0x01, 0x48, 0xe8, 0x26, 0x03, 0x8e, 0x27, 0x4d, 0x96, 0x72}} , + {{0xc8, 0x09, 0x3b, 0x60, 0xc9, 0x26, 0x4d, 0x7c, 0xf2, 0x9c, 0xd4, 0xa1, 0x3b, 0x26, 0xc2, 0x04, 0x33, 0x44, 0x76, 0x3c, 0x02, 0xbb, 0x11, 0x42, 0x0c, 0x22, 0xb7, 0xc6, 0xe1, 0xac, 0xb4, 0x0e}}}, +{{{0x6f, 0x85, 0xe7, 0xef, 0xde, 0x67, 0x30, 0xfc, 0xbf, 0x5a, 0xe0, 0x7b, 0x7a, 0x2a, 0x54, 0x6b, 0x5d, 0x62, 0x85, 0xa1, 0xf8, 0x16, 0x88, 0xec, 0x61, 0xb9, 0x96, 0xb5, 0xef, 0x2d, 0x43, 0x4d}} , + {{0x7c, 0x31, 0x33, 0xcc, 0xe4, 0xcf, 0x6c, 0xff, 0x80, 0x47, 0x77, 0xd1, 0xd8, 0xe9, 0x69, 0x97, 0x98, 0x7f, 0x20, 0x57, 0x1d, 0x1d, 0x4f, 0x08, 0x27, 0xc8, 0x35, 0x57, 0x40, 0xc6, 0x21, 0x0c}}}, +{{{0xd2, 0x8e, 0x9b, 0xfa, 0x42, 0x8e, 0xdf, 0x8f, 0xc7, 0x86, 0xf9, 0xa4, 0xca, 0x70, 0x00, 0x9d, 0x21, 0xbf, 0xec, 0x57, 0x62, 0x30, 0x58, 0x8c, 0x0d, 0x35, 0xdb, 0x5d, 0x8b, 0x6a, 0xa0, 0x5a}} , + {{0xc1, 0x58, 0x7c, 0x0d, 0x20, 0xdd, 0x11, 0x26, 0x5f, 0x89, 0x3b, 0x97, 0x58, 0xf8, 0x8b, 0xe3, 0xdf, 0x32, 0xe2, 0xfc, 0xd8, 0x67, 0xf2, 0xa5, 0x37, 0x1e, 0x6d, 0xec, 0x7c, 0x27, 0x20, 0x79}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd0, 0xe9, 0xc0, 0xfa, 0x95, 0x45, 0x23, 0x96, 0xf1, 0x2c, 0x79, 0x25, 0x14, 0xce, 0x40, 0x14, 0x44, 0x2c, 0x36, 0x50, 0xd9, 0x63, 0x56, 0xb7, 0x56, 0x3b, 0x9e, 0xa7, 0xef, 0x89, 0xbb, 0x0e}} , + {{0xce, 0x7f, 0xdc, 0x0a, 0xcc, 0x82, 0x1c, 0x0a, 0x78, 0x71, 0xe8, 0x74, 0x8d, 0x01, 0x30, 0x0f, 0xa7, 0x11, 0x4c, 0xdf, 0x38, 0xd7, 0xa7, 0x0d, 0xf8, 0x48, 0x52, 0x00, 0x80, 0x7b, 0x5f, 0x0e}}}, +{{{0x25, 0x83, 0xe6, 0x94, 0x7b, 0x81, 0xb2, 0x91, 0xae, 0x0e, 0x05, 0xc9, 0xa3, 0x68, 0x2d, 0xd9, 0x88, 0x25, 0x19, 0x2a, 0x61, 0x61, 0x21, 0x97, 0x15, 0xa1, 0x35, 0xa5, 0x46, 0xc8, 0xa2, 0x0e}} , + {{0x1b, 0x03, 0x0d, 0x8b, 0x5a, 0x1b, 0x97, 0x4b, 0xf2, 0x16, 0x31, 0x3d, 0x1f, 0x33, 0xa0, 0x50, 0x3a, 0x18, 0xbe, 0x13, 0xa1, 0x76, 0xc1, 0xba, 0x1b, 0xf1, 0x05, 0x7b, 0x33, 0xa8, 0x82, 0x3b}}}, +{{{0xba, 0x36, 0x7b, 0x6d, 0xa9, 0xea, 0x14, 0x12, 0xc5, 0xfa, 0x91, 0x00, 0xba, 0x9b, 0x99, 0xcc, 0x56, 0x02, 0xe9, 0xa0, 0x26, 0x40, 0x66, 0x8c, 0xc4, 0xf8, 0x85, 0x33, 0x68, 0xe7, 0x03, 0x20}} , + {{0x50, 0x5b, 0xff, 0xa9, 0xb2, 0xf1, 0xf1, 0x78, 0xcf, 0x14, 0xa4, 0xa9, 0xfc, 0x09, 0x46, 0x94, 0x54, 0x65, 0x0d, 0x9c, 0x5f, 0x72, 0x21, 0xe2, 0x97, 0xa5, 0x2d, 0x81, 0xce, 0x4a, 0x5f, 0x79}}}, +{{{0x3d, 0x5f, 0x5c, 0xd2, 0xbc, 0x7d, 0x77, 0x0e, 0x2a, 0x6d, 0x22, 0x45, 0x84, 0x06, 0xc4, 0xdd, 0xc6, 0xa6, 0xc6, 0xd7, 0x49, 0xad, 0x6d, 0x87, 0x91, 0x0e, 0x3a, 0x67, 0x1d, 0x2c, 0x1d, 0x56}} , + {{0xfe, 0x7a, 0x74, 0xcf, 0xd4, 0xd2, 0xe5, 0x19, 0xde, 0xd0, 0xdb, 0x70, 0x23, 0x69, 0xe6, 0x6d, 0xec, 0xec, 0xcc, 0x09, 0x33, 0x6a, 0x77, 0xdc, 0x6b, 0x22, 0x76, 0x5d, 0x92, 0x09, 0xac, 0x2d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x23, 0x15, 0x17, 0xeb, 0xd3, 0xdb, 0x12, 0x5e, 0x01, 0xf0, 0x91, 0xab, 0x2c, 0x41, 0xce, 0xac, 0xed, 0x1b, 0x4b, 0x2d, 0xbc, 0xdb, 0x17, 0x66, 0x89, 0x46, 0xad, 0x4b, 0x1e, 0x6f, 0x0b, 0x14}} , + {{0x11, 0xce, 0xbf, 0xb6, 0x77, 0x2d, 0x48, 0x22, 0x18, 0x4f, 0xa3, 0x5d, 0x4a, 0xb0, 0x70, 0x12, 0x3e, 0x54, 0xd7, 0xd8, 0x0e, 0x2b, 0x27, 0xdc, 0x53, 0xff, 0xca, 0x8c, 0x59, 0xb3, 0x4e, 0x44}}}, +{{{0x07, 0x76, 0x61, 0x0f, 0x66, 0xb2, 0x21, 0x39, 0x7e, 0xc0, 0xec, 0x45, 0x28, 0x82, 0xa1, 0x29, 0x32, 0x44, 0x35, 0x13, 0x5e, 0x61, 0x5e, 0x54, 0xcb, 0x7c, 0xef, 0xf6, 0x41, 0xcf, 0x9f, 0x0a}} , + {{0xdd, 0xf9, 0xda, 0x84, 0xc3, 0xe6, 0x8a, 0x9f, 0x24, 0xd2, 0x96, 0x5d, 0x39, 0x6f, 0x58, 0x8c, 0xc1, 0x56, 0x93, 0xab, 0xb5, 0x79, 0x3b, 0xd2, 0xa8, 0x73, 0x16, 0xed, 0xfa, 0xb4, 0x2f, 0x73}}}, +{{{0x8b, 0xb1, 0x95, 0xe5, 0x92, 0x50, 0x35, 0x11, 0x76, 0xac, 0xf4, 0x4d, 0x24, 0xc3, 0x32, 0xe6, 0xeb, 0xfe, 0x2c, 0x87, 0xc4, 0xf1, 0x56, 0xc4, 0x75, 0x24, 0x7a, 0x56, 0x85, 0x5a, 0x3a, 0x13}} , + {{0x0d, 0x16, 0xac, 0x3c, 0x4a, 0x58, 0x86, 0x3a, 0x46, 0x7f, 0x6c, 0xa3, 0x52, 0x6e, 0x37, 0xe4, 0x96, 0x9c, 0xe9, 0x5c, 0x66, 0x41, 0x67, 0xe4, 0xfb, 0x79, 0x0c, 0x05, 0xf6, 0x64, 0xd5, 0x7c}}}, +{{{0x28, 0xc1, 0xe1, 0x54, 0x73, 0xf2, 0xbf, 0x76, 0x74, 0x19, 0x19, 0x1b, 0xe4, 0xb9, 0xa8, 0x46, 0x65, 0x73, 0xf3, 0x77, 0x9b, 0x29, 0x74, 0x5b, 0xc6, 0x89, 0x6c, 0x2c, 0x7c, 0xf8, 0xb3, 0x0f}} , + {{0xf7, 0xd5, 0xe9, 0x74, 0x5d, 0xb8, 0x25, 0x16, 0xb5, 0x30, 0xbc, 0x84, 0xc5, 0xf0, 0xad, 0xca, 0x12, 0x28, 0xbc, 0x9d, 0xd4, 0xfa, 0x82, 0xe6, 0xe3, 0xbf, 0xa2, 0x15, 0x2c, 0xd4, 0x34, 0x10}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x61, 0xb1, 0x46, 0xba, 0x0e, 0x31, 0xa5, 0x67, 0x6c, 0x7f, 0xd6, 0xd9, 0x27, 0x85, 0x0f, 0x79, 0x14, 0xc8, 0x6c, 0x2f, 0x5f, 0x5b, 0x9c, 0x35, 0x3d, 0x38, 0x86, 0x77, 0x65, 0x55, 0x6a, 0x7b}} , + {{0xd3, 0xb0, 0x3a, 0x66, 0x60, 0x1b, 0x43, 0xf1, 0x26, 0x58, 0x99, 0x09, 0x8f, 0x2d, 0xa3, 0x14, 0x71, 0x85, 0xdb, 0xed, 0xf6, 0x26, 0xd5, 0x61, 0x9a, 0x73, 0xac, 0x0e, 0xea, 0xac, 0xb7, 0x0c}}}, +{{{0x5e, 0xf4, 0xe5, 0x17, 0x0e, 0x10, 0x9f, 0xe7, 0x43, 0x5f, 0x67, 0x5c, 0xac, 0x4b, 0xe5, 0x14, 0x41, 0xd2, 0xbf, 0x48, 0xf5, 0x14, 0xb0, 0x71, 0xc6, 0x61, 0xc1, 0xb2, 0x70, 0x58, 0xd2, 0x5a}} , + {{0x2d, 0xba, 0x16, 0x07, 0x92, 0x94, 0xdc, 0xbd, 0x50, 0x2b, 0xc9, 0x7f, 0x42, 0x00, 0xba, 0x61, 0xed, 0xf8, 0x43, 0xed, 0xf5, 0xf9, 0x40, 0x60, 0xb2, 0xb0, 0x82, 0xcb, 0xed, 0x75, 0xc7, 0x65}}}, +{{{0x80, 0xba, 0x0d, 0x09, 0x40, 0xa7, 0x39, 0xa6, 0x67, 0x34, 0x7e, 0x66, 0xbe, 0x56, 0xfb, 0x53, 0x78, 0xc4, 0x46, 0xe8, 0xed, 0x68, 0x6c, 0x7f, 0xce, 0xe8, 0x9f, 0xce, 0xa2, 0x64, 0x58, 0x53}} , + {{0xe8, 0xc1, 0xa9, 0xc2, 0x7b, 0x59, 0x21, 0x33, 0xe2, 0x43, 0x73, 0x2b, 0xac, 0x2d, 0xc1, 0x89, 0x3b, 0x15, 0xe2, 0xd5, 0xc0, 0x97, 0x8a, 0xfd, 0x6f, 0x36, 0x33, 0xb7, 0xb9, 0xc3, 0x88, 0x09}}}, +{{{0xd0, 0xb6, 0x56, 0x30, 0x5c, 0xae, 0xb3, 0x75, 0x44, 0xa4, 0x83, 0x51, 0x6e, 0x01, 0x65, 0xef, 0x45, 0x76, 0xe6, 0xf5, 0xa2, 0x0d, 0xd4, 0x16, 0x3b, 0x58, 0x2f, 0xf2, 0x2f, 0x36, 0x18, 0x3f}} , + {{0xfd, 0x2f, 0xe0, 0x9b, 0x1e, 0x8c, 0xc5, 0x18, 0xa9, 0xca, 0xd4, 0x2b, 0x35, 0xb6, 0x95, 0x0a, 0x9f, 0x7e, 0xfb, 0xc4, 0xef, 0x88, 0x7b, 0x23, 0x43, 0xec, 0x2f, 0x0d, 0x0f, 0x7a, 0xfc, 0x5c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x8d, 0xd2, 0xda, 0xc7, 0x44, 0xd6, 0x7a, 0xdb, 0x26, 0x7d, 0x1d, 0xb8, 0xe1, 0xde, 0x9d, 0x7a, 0x7d, 0x17, 0x7e, 0x1c, 0x37, 0x04, 0x8d, 0x2d, 0x7c, 0x5e, 0x18, 0x38, 0x1e, 0xaf, 0xc7, 0x1b}} , + {{0x33, 0x48, 0x31, 0x00, 0x59, 0xf6, 0xf2, 0xca, 0x0f, 0x27, 0x1b, 0x63, 0x12, 0x7e, 0x02, 0x1d, 0x49, 0xc0, 0x5d, 0x79, 0x87, 0xef, 0x5e, 0x7a, 0x2f, 0x1f, 0x66, 0x55, 0xd8, 0x09, 0xd9, 0x61}}}, +{{{0x54, 0x83, 0x02, 0x18, 0x82, 0x93, 0x99, 0x07, 0xd0, 0xa7, 0xda, 0xd8, 0x75, 0x89, 0xfa, 0xf2, 0xd9, 0xa3, 0xb8, 0x6b, 0x5a, 0x35, 0x28, 0xd2, 0x6b, 0x59, 0xc2, 0xf8, 0x45, 0xe2, 0xbc, 0x06}} , + {{0x65, 0xc0, 0xa3, 0x88, 0x51, 0x95, 0xfc, 0x96, 0x94, 0x78, 0xe8, 0x0d, 0x8b, 0x41, 0xc9, 0xc2, 0x58, 0x48, 0x75, 0x10, 0x2f, 0xcd, 0x2a, 0xc9, 0xa0, 0x6d, 0x0f, 0xdd, 0x9c, 0x98, 0x26, 0x3d}}}, +{{{0x2f, 0x66, 0x29, 0x1b, 0x04, 0x89, 0xbd, 0x7e, 0xee, 0x6e, 0xdd, 0xb7, 0x0e, 0xef, 0xb0, 0x0c, 0xb4, 0xfc, 0x7f, 0xc2, 0xc9, 0x3a, 0x3c, 0x64, 0xef, 0x45, 0x44, 0xaf, 0x8a, 0x90, 0x65, 0x76}} , + {{0xa1, 0x4c, 0x70, 0x4b, 0x0e, 0xa0, 0x83, 0x70, 0x13, 0xa4, 0xaf, 0xb8, 0x38, 0x19, 0x22, 0x65, 0x09, 0xb4, 0x02, 0x4f, 0x06, 0xf8, 0x17, 0xce, 0x46, 0x45, 0xda, 0x50, 0x7c, 0x8a, 0xd1, 0x4e}}}, +{{{0xf7, 0xd4, 0x16, 0x6c, 0x4e, 0x95, 0x9d, 0x5d, 0x0f, 0x91, 0x2b, 0x52, 0xfe, 0x5c, 0x34, 0xe5, 0x30, 0xe6, 0xa4, 0x3b, 0xf3, 0xf3, 0x34, 0x08, 0xa9, 0x4a, 0xa0, 0xb5, 0x6e, 0xb3, 0x09, 0x0a}} , + {{0x26, 0xd9, 0x5e, 0xa3, 0x0f, 0xeb, 0xa2, 0xf3, 0x20, 0x3b, 0x37, 0xd4, 0xe4, 0x9e, 0xce, 0x06, 0x3d, 0x53, 0xed, 0xae, 0x2b, 0xeb, 0xb6, 0x24, 0x0a, 0x11, 0xa3, 0x0f, 0xd6, 0x7f, 0xa4, 0x3a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xdb, 0x9f, 0x2c, 0xfc, 0xd6, 0xb2, 0x1e, 0x2e, 0x52, 0x7a, 0x06, 0x87, 0x2d, 0x86, 0x72, 0x2b, 0x6d, 0x90, 0x77, 0x46, 0x43, 0xb5, 0x7a, 0xf8, 0x60, 0x7d, 0x91, 0x60, 0x5b, 0x9d, 0x9e, 0x07}} , + {{0x97, 0x87, 0xc7, 0x04, 0x1c, 0x38, 0x01, 0x39, 0x58, 0xc7, 0x85, 0xa3, 0xfc, 0x64, 0x00, 0x64, 0x25, 0xa2, 0xbf, 0x50, 0x94, 0xca, 0x26, 0x31, 0x45, 0x0a, 0x24, 0xd2, 0x51, 0x29, 0x51, 0x16}}}, +{{{0x4d, 0x4a, 0xd7, 0x98, 0x71, 0x57, 0xac, 0x7d, 0x8b, 0x37, 0xbd, 0x63, 0xff, 0x87, 0xb1, 0x49, 0x95, 0x20, 0x7c, 0xcf, 0x7c, 0x59, 0xc4, 0x91, 0x9c, 0xef, 0xd0, 0xdb, 0x60, 0x09, 0x9d, 0x46}} , + {{0xcb, 0x78, 0x94, 0x90, 0xe4, 0x45, 0xb3, 0xf6, 0xd9, 0xf6, 0x57, 0x74, 0xd5, 0xf8, 0x83, 0x4f, 0x39, 0xc9, 0xbd, 0x88, 0xc2, 0x57, 0x21, 0x1f, 0x24, 0x32, 0x68, 0xf8, 0xc7, 0x21, 0x5f, 0x0b}}}, +{{{0x2a, 0x36, 0x68, 0xfc, 0x5f, 0xb6, 0x4f, 0xa5, 0xe3, 0x9d, 0x24, 0x2f, 0xc0, 0x93, 0x61, 0xcf, 0xf8, 0x0a, 0xed, 0xe1, 0xdb, 0x27, 0xec, 0x0e, 0x14, 0x32, 0x5f, 0x8e, 0xa1, 0x62, 0x41, 0x16}} , + {{0x95, 0x21, 0x01, 0xce, 0x95, 0x5b, 0x0e, 0x57, 0xc7, 0xb9, 0x62, 0xb5, 0x28, 0xca, 0x11, 0xec, 0xb4, 0x46, 0x06, 0x73, 0x26, 0xff, 0xfb, 0x66, 0x7d, 0xee, 0x5f, 0xb2, 0x56, 0xfd, 0x2a, 0x08}}}, +{{{0x92, 0x67, 0x77, 0x56, 0xa1, 0xff, 0xc4, 0xc5, 0x95, 0xf0, 0xe3, 0x3a, 0x0a, 0xca, 0x94, 0x4d, 0x9e, 0x7e, 0x3d, 0xb9, 0x6e, 0xb6, 0xb0, 0xce, 0xa4, 0x30, 0x89, 0x99, 0xe9, 0xad, 0x11, 0x59}} , + {{0xf6, 0x48, 0x95, 0xa1, 0x6f, 0x5f, 0xb7, 0xa5, 0xbb, 0x30, 0x00, 0x1c, 0xd2, 0x8a, 0xd6, 0x25, 0x26, 0x1b, 0xb2, 0x0d, 0x37, 0x6a, 0x05, 0xf4, 0x9d, 0x3e, 0x17, 0x2a, 0x43, 0xd2, 0x3a, 0x06}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x32, 0x99, 0x93, 0xd1, 0x9a, 0x72, 0xf3, 0xa9, 0x16, 0xbd, 0xb4, 0x4c, 0xdd, 0xf9, 0xd4, 0xb2, 0x64, 0x9a, 0xd3, 0x05, 0xe4, 0xa3, 0x73, 0x1c, 0xcb, 0x7e, 0x57, 0x67, 0xff, 0x04, 0xb3, 0x10}} , + {{0xb9, 0x4b, 0xa4, 0xad, 0xd0, 0x6d, 0x61, 0x23, 0xb4, 0xaf, 0x34, 0xa9, 0xaa, 0x65, 0xec, 0xd9, 0x69, 0xe3, 0x85, 0xcd, 0xcc, 0xe7, 0xb0, 0x9b, 0x41, 0xc1, 0x1c, 0xf9, 0xa0, 0xfa, 0xb7, 0x13}}}, +{{{0x04, 0xfd, 0x88, 0x3c, 0x0c, 0xd0, 0x09, 0x52, 0x51, 0x4f, 0x06, 0x19, 0xcc, 0xc3, 0xbb, 0xde, 0x80, 0xc5, 0x33, 0xbc, 0xf9, 0xf3, 0x17, 0x36, 0xdd, 0xc6, 0xde, 0xe8, 0x9b, 0x5d, 0x79, 0x1b}} , + {{0x65, 0x0a, 0xbe, 0x51, 0x57, 0xad, 0x50, 0x79, 0x08, 0x71, 0x9b, 0x07, 0x95, 0x8f, 0xfb, 0xae, 0x4b, 0x38, 0xba, 0xcf, 0x53, 0x2a, 0x86, 0x1e, 0xc0, 0x50, 0x5c, 0x67, 0x1b, 0xf6, 0x87, 0x6c}}}, +{{{0x4f, 0x00, 0xb2, 0x66, 0x55, 0xed, 0x4a, 0xed, 0x8d, 0xe1, 0x66, 0x18, 0xb2, 0x14, 0x74, 0x8d, 0xfd, 0x1a, 0x36, 0x0f, 0x26, 0x5c, 0x8b, 0x89, 0xf3, 0xab, 0xf2, 0xf3, 0x24, 0x67, 0xfd, 0x70}} , + {{0xfd, 0x4e, 0x2a, 0xc1, 0x3a, 0xca, 0x8f, 0x00, 0xd8, 0xec, 0x74, 0x67, 0xef, 0x61, 0xe0, 0x28, 0xd0, 0x96, 0xf4, 0x48, 0xde, 0x81, 0xe3, 0xef, 0xdc, 0xaa, 0x7d, 0xf3, 0xb6, 0x55, 0xa6, 0x65}}}, +{{{0xeb, 0xcb, 0xc5, 0x70, 0x91, 0x31, 0x10, 0x93, 0x0d, 0xc8, 0xd0, 0xef, 0x62, 0xe8, 0x6f, 0x82, 0xe3, 0x69, 0x3d, 0x91, 0x7f, 0x31, 0xe1, 0x26, 0x35, 0x3c, 0x4a, 0x2f, 0xab, 0xc4, 0x9a, 0x5e}} , + {{0xab, 0x1b, 0xb5, 0xe5, 0x2b, 0xc3, 0x0e, 0x29, 0xb0, 0xd0, 0x73, 0xe6, 0x4f, 0x64, 0xf2, 0xbc, 0xe4, 0xe4, 0xe1, 0x9a, 0x52, 0x33, 0x2f, 0xbd, 0xcc, 0x03, 0xee, 0x8a, 0xfa, 0x00, 0x5f, 0x50}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xf6, 0xdb, 0x0d, 0x22, 0x3d, 0xb5, 0x14, 0x75, 0x31, 0xf0, 0x81, 0xe2, 0xb9, 0x37, 0xa2, 0xa9, 0x84, 0x11, 0x9a, 0x07, 0xb5, 0x53, 0x89, 0x78, 0xa9, 0x30, 0x27, 0xa1, 0xf1, 0x4e, 0x5c, 0x2e}} , + {{0x8b, 0x00, 0x54, 0xfb, 0x4d, 0xdc, 0xcb, 0x17, 0x35, 0x40, 0xff, 0xb7, 0x8c, 0xfe, 0x4a, 0xe4, 0x4e, 0x99, 0x4e, 0xa8, 0x74, 0x54, 0x5d, 0x5c, 0x96, 0xa3, 0x12, 0x55, 0x36, 0x31, 0x17, 0x5c}}}, +{{{0xce, 0x24, 0xef, 0x7b, 0x86, 0xf2, 0x0f, 0x77, 0xe8, 0x5c, 0x7d, 0x87, 0x38, 0x2d, 0xef, 0xaf, 0xf2, 0x8c, 0x72, 0x2e, 0xeb, 0xb6, 0x55, 0x4b, 0x6e, 0xf1, 0x4e, 0x8a, 0x0e, 0x9a, 0x6c, 0x4c}} , + {{0x25, 0xea, 0x86, 0xc2, 0xd1, 0x4f, 0xb7, 0x3e, 0xa8, 0x5c, 0x8d, 0x66, 0x81, 0x25, 0xed, 0xc5, 0x4c, 0x05, 0xb9, 0xd8, 0xd6, 0x70, 0xbe, 0x73, 0x82, 0xe8, 0xa1, 0xe5, 0x1e, 0x71, 0xd5, 0x26}}}, +{{{0x4e, 0x6d, 0xc3, 0xa7, 0x4f, 0x22, 0x45, 0x26, 0xa2, 0x7e, 0x16, 0xf7, 0xf7, 0x63, 0xdc, 0x86, 0x01, 0x2a, 0x71, 0x38, 0x5c, 0x33, 0xc3, 0xce, 0x30, 0xff, 0xf9, 0x2c, 0x91, 0x71, 0x8a, 0x72}} , + {{0x8c, 0x44, 0x09, 0x28, 0xd5, 0x23, 0xc9, 0x8f, 0xf3, 0x84, 0x45, 0xc6, 0x9a, 0x5e, 0xff, 0xd2, 0xc7, 0x57, 0x93, 0xa3, 0xc1, 0x69, 0xdd, 0x62, 0x0f, 0xda, 0x5c, 0x30, 0x59, 0x5d, 0xe9, 0x4c}}}, +{{{0x92, 0x7e, 0x50, 0x27, 0x72, 0xd7, 0x0c, 0xd6, 0x69, 0x96, 0x81, 0x35, 0x84, 0x94, 0x35, 0x8b, 0x6c, 0xaa, 0x62, 0x86, 0x6e, 0x1c, 0x15, 0xf3, 0x6c, 0xb3, 0xff, 0x65, 0x1b, 0xa2, 0x9b, 0x59}} , + {{0xe2, 0xa9, 0x65, 0x88, 0xc4, 0x50, 0xfa, 0xbb, 0x3b, 0x6e, 0x5f, 0x44, 0x01, 0xca, 0x97, 0xd4, 0xdd, 0xf6, 0xcd, 0x3f, 0x3f, 0xe5, 0x97, 0x67, 0x2b, 0x8c, 0x66, 0x0f, 0x35, 0x9b, 0xf5, 0x07}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xf1, 0x59, 0x27, 0xd8, 0xdb, 0x5a, 0x11, 0x5e, 0x82, 0xf3, 0x38, 0xff, 0x1c, 0xed, 0xfe, 0x3f, 0x64, 0x54, 0x3f, 0x7f, 0xd1, 0x81, 0xed, 0xef, 0x65, 0xc5, 0xcb, 0xfd, 0xe1, 0x80, 0xcd, 0x11}} , + {{0xe0, 0xdb, 0x22, 0x28, 0xe6, 0xff, 0x61, 0x9d, 0x41, 0x14, 0x2d, 0x3b, 0x26, 0x22, 0xdf, 0xf1, 0x34, 0x81, 0xe9, 0x45, 0xee, 0x0f, 0x98, 0x8b, 0xa6, 0x3f, 0xef, 0xf7, 0x43, 0x19, 0xf1, 0x43}}}, +{{{0xee, 0xf3, 0x00, 0xa1, 0x50, 0xde, 0xc0, 0xb6, 0x01, 0xe3, 0x8c, 0x3c, 0x4d, 0x31, 0xd2, 0xb0, 0x58, 0xcd, 0xed, 0x10, 0x4a, 0x7a, 0xef, 0x80, 0xa9, 0x19, 0x32, 0xf3, 0xd8, 0x33, 0x8c, 0x06}} , + {{0xcb, 0x7d, 0x4f, 0xff, 0x30, 0xd8, 0x12, 0x3b, 0x39, 0x1c, 0x06, 0xf9, 0x4c, 0x34, 0x35, 0x71, 0xb5, 0x16, 0x94, 0x67, 0xdf, 0xee, 0x11, 0xde, 0xa4, 0x1d, 0x88, 0x93, 0x35, 0xa9, 0x32, 0x10}}}, +{{{0xe9, 0xc3, 0xbc, 0x7b, 0x5c, 0xfc, 0xb2, 0xf9, 0xc9, 0x2f, 0xe5, 0xba, 0x3a, 0x0b, 0xab, 0x64, 0x38, 0x6f, 0x5b, 0x4b, 0x93, 0xda, 0x64, 0xec, 0x4d, 0x3d, 0xa0, 0xf5, 0xbb, 0xba, 0x47, 0x48}} , + {{0x60, 0xbc, 0x45, 0x1f, 0x23, 0xa2, 0x3b, 0x70, 0x76, 0xe6, 0x97, 0x99, 0x4f, 0x77, 0x54, 0x67, 0x30, 0x9a, 0xe7, 0x66, 0xd6, 0xcd, 0x2e, 0x51, 0x24, 0x2c, 0x42, 0x4a, 0x11, 0xfe, 0x6f, 0x7e}}}, +{{{0x87, 0xc0, 0xb1, 0xf0, 0xa3, 0x6f, 0x0c, 0x93, 0xa9, 0x0a, 0x72, 0xef, 0x5c, 0xbe, 0x65, 0x35, 0xa7, 0x6a, 0x4e, 0x2c, 0xbf, 0x21, 0x23, 0xe8, 0x2f, 0x97, 0xc7, 0x3e, 0xc8, 0x17, 0xac, 0x1e}} , + {{0x7b, 0xef, 0x21, 0xe5, 0x40, 0xcc, 0x1e, 0xdc, 0xd6, 0xbd, 0x97, 0x7a, 0x7c, 0x75, 0x86, 0x7a, 0x25, 0x5a, 0x6e, 0x7c, 0xe5, 0x51, 0x3c, 0x1b, 0x5b, 0x82, 0x9a, 0x07, 0x60, 0xa1, 0x19, 0x04}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x96, 0x88, 0xa6, 0xab, 0x8f, 0xe3, 0x3a, 0x49, 0xf8, 0xfe, 0x34, 0xe7, 0x6a, 0xb2, 0xfe, 0x40, 0x26, 0x74, 0x57, 0x4c, 0xf6, 0xd4, 0x99, 0xce, 0x5d, 0x7b, 0x2f, 0x67, 0xd6, 0x5a, 0xe4, 0x4e}} , + {{0x5c, 0x82, 0xb3, 0xbd, 0x55, 0x25, 0xf6, 0x6a, 0x93, 0xa4, 0x02, 0xc6, 0x7d, 0x5c, 0xb1, 0x2b, 0x5b, 0xff, 0xfb, 0x56, 0xf8, 0x01, 0x41, 0x90, 0xc6, 0xb6, 0xac, 0x4f, 0xfe, 0xa7, 0x41, 0x70}}}, +{{{0xdb, 0xfa, 0x9b, 0x2c, 0xd4, 0x23, 0x67, 0x2c, 0x8a, 0x63, 0x6c, 0x07, 0x26, 0x48, 0x4f, 0xc2, 0x03, 0xd2, 0x53, 0x20, 0x28, 0xed, 0x65, 0x71, 0x47, 0xa9, 0x16, 0x16, 0x12, 0xbc, 0x28, 0x33}} , + {{0x39, 0xc0, 0xfa, 0xfa, 0xcd, 0x33, 0x43, 0xc7, 0x97, 0x76, 0x9b, 0x93, 0x91, 0x72, 0xeb, 0xc5, 0x18, 0x67, 0x4c, 0x11, 0xf0, 0xf4, 0xe5, 0x73, 0xb2, 0x5c, 0x1b, 0xc2, 0x26, 0x3f, 0xbf, 0x2b}}}, +{{{0x86, 0xe6, 0x8c, 0x1d, 0xdf, 0xca, 0xfc, 0xd5, 0xf8, 0x3a, 0xc3, 0x44, 0x72, 0xe6, 0x78, 0x9d, 0x2b, 0x97, 0xf8, 0x28, 0x45, 0xb4, 0x20, 0xc9, 0x2a, 0x8c, 0x67, 0xaa, 0x11, 0xc5, 0x5b, 0x2f}} , + {{0x17, 0x0f, 0x86, 0x52, 0xd7, 0x9d, 0xc3, 0x44, 0x51, 0x76, 0x32, 0x65, 0xb4, 0x37, 0x81, 0x99, 0x46, 0x37, 0x62, 0xed, 0xcf, 0x64, 0x9d, 0x72, 0x40, 0x7a, 0x4c, 0x0b, 0x76, 0x2a, 0xfb, 0x56}}}, +{{{0x33, 0xa7, 0x90, 0x7c, 0xc3, 0x6f, 0x17, 0xa5, 0xa0, 0x67, 0x72, 0x17, 0xea, 0x7e, 0x63, 0x14, 0x83, 0xde, 0xc1, 0x71, 0x2d, 0x41, 0x32, 0x7a, 0xf3, 0xd1, 0x2b, 0xd8, 0x2a, 0xa6, 0x46, 0x36}} , + {{0xac, 0xcc, 0x6b, 0x7c, 0xf9, 0xb8, 0x8b, 0x08, 0x5c, 0xd0, 0x7d, 0x8f, 0x73, 0xea, 0x20, 0xda, 0x86, 0xca, 0x00, 0xc7, 0xad, 0x73, 0x4d, 0xe9, 0xe8, 0xa9, 0xda, 0x1f, 0x03, 0x06, 0xdd, 0x24}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x9c, 0xb2, 0x61, 0x0a, 0x98, 0x2a, 0xa5, 0xd7, 0xee, 0xa9, 0xac, 0x65, 0xcb, 0x0a, 0x1e, 0xe2, 0xbe, 0xdc, 0x85, 0x59, 0x0f, 0x9c, 0xa6, 0x57, 0x34, 0xa5, 0x87, 0xeb, 0x7b, 0x1e, 0x0c, 0x3c}} , + {{0x2f, 0xbd, 0x84, 0x63, 0x0d, 0xb5, 0xa0, 0xf0, 0x4b, 0x9e, 0x93, 0xc6, 0x34, 0x9a, 0x34, 0xff, 0x73, 0x19, 0x2f, 0x6e, 0x54, 0x45, 0x2c, 0x92, 0x31, 0x76, 0x34, 0xf1, 0xb2, 0x26, 0xe8, 0x74}}}, +{{{0x0a, 0x67, 0x90, 0x6d, 0x0c, 0x4c, 0xcc, 0xc0, 0xe6, 0xbd, 0xa7, 0x5e, 0x55, 0x8c, 0xcd, 0x58, 0x9b, 0x11, 0xa2, 0xbb, 0x4b, 0xb1, 0x43, 0x04, 0x3c, 0x55, 0xed, 0x23, 0xfe, 0xcd, 0xb1, 0x53}} , + {{0x05, 0xfb, 0x75, 0xf5, 0x01, 0xaf, 0x38, 0x72, 0x58, 0xfc, 0x04, 0x29, 0x34, 0x7a, 0x67, 0xa2, 0x08, 0x50, 0x6e, 0xd0, 0x2b, 0x73, 0xd5, 0xb8, 0xe4, 0x30, 0x96, 0xad, 0x45, 0xdf, 0xa6, 0x5c}}}, +{{{0x0d, 0x88, 0x1a, 0x90, 0x7e, 0xdc, 0xd8, 0xfe, 0xc1, 0x2f, 0x5d, 0x67, 0xee, 0x67, 0x2f, 0xed, 0x6f, 0x55, 0x43, 0x5f, 0x87, 0x14, 0x35, 0x42, 0xd3, 0x75, 0xae, 0xd5, 0xd3, 0x85, 0x1a, 0x76}} , + {{0x87, 0xc8, 0xa0, 0x6e, 0xe1, 0xb0, 0xad, 0x6a, 0x4a, 0x34, 0x71, 0xed, 0x7c, 0xd6, 0x44, 0x03, 0x65, 0x4a, 0x5c, 0x5c, 0x04, 0xf5, 0x24, 0x3f, 0xb0, 0x16, 0x5e, 0x8c, 0xb2, 0xd2, 0xc5, 0x20}}}, +{{{0x98, 0x83, 0xc2, 0x37, 0xa0, 0x41, 0xa8, 0x48, 0x5c, 0x5f, 0xbf, 0xc8, 0xfa, 0x24, 0xe0, 0x59, 0x2c, 0xbd, 0xf6, 0x81, 0x7e, 0x88, 0xe6, 0xca, 0x04, 0xd8, 0x5d, 0x60, 0xbb, 0x74, 0xa7, 0x0b}} , + {{0x21, 0x13, 0x91, 0xbf, 0x77, 0x7a, 0x33, 0xbc, 0xe9, 0x07, 0x39, 0x0a, 0xdd, 0x7d, 0x06, 0x10, 0x9a, 0xee, 0x47, 0x73, 0x1b, 0x15, 0x5a, 0xfb, 0xcd, 0x4d, 0xd0, 0xd2, 0x3a, 0x01, 0xba, 0x54}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x48, 0xd5, 0x39, 0x4a, 0x0b, 0x20, 0x6a, 0x43, 0xa0, 0x07, 0x82, 0x5e, 0x49, 0x7c, 0xc9, 0x47, 0xf1, 0x7c, 0x37, 0xb9, 0x23, 0xef, 0x6b, 0x46, 0x45, 0x8c, 0x45, 0x76, 0xdf, 0x14, 0x6b, 0x6e}} , + {{0x42, 0xc9, 0xca, 0x29, 0x4c, 0x76, 0x37, 0xda, 0x8a, 0x2d, 0x7c, 0x3a, 0x58, 0xf2, 0x03, 0xb4, 0xb5, 0xb9, 0x1a, 0x13, 0x2d, 0xde, 0x5f, 0x6b, 0x9d, 0xba, 0x52, 0xc9, 0x5d, 0xb3, 0xf3, 0x30}}}, +{{{0x4c, 0x6f, 0xfe, 0x6b, 0x0c, 0x62, 0xd7, 0x48, 0x71, 0xef, 0xb1, 0x85, 0x79, 0xc0, 0xed, 0x24, 0xb1, 0x08, 0x93, 0x76, 0x8e, 0xf7, 0x38, 0x8e, 0xeb, 0xfe, 0x80, 0x40, 0xaf, 0x90, 0x64, 0x49}} , + {{0x4a, 0x88, 0xda, 0xc1, 0x98, 0x44, 0x3c, 0x53, 0x4e, 0xdb, 0x4b, 0xb9, 0x12, 0x5f, 0xcd, 0x08, 0x04, 0xef, 0x75, 0xe7, 0xb1, 0x3a, 0xe5, 0x07, 0xfa, 0xca, 0x65, 0x7b, 0x72, 0x10, 0x64, 0x7f}}}, +{{{0x3d, 0x81, 0xf0, 0xeb, 0x16, 0xfd, 0x58, 0x33, 0x8d, 0x7c, 0x1a, 0xfb, 0x20, 0x2c, 0x8a, 0xee, 0x90, 0xbb, 0x33, 0x6d, 0x45, 0xe9, 0x8e, 0x99, 0x85, 0xe1, 0x08, 0x1f, 0xc5, 0xf1, 0xb5, 0x46}} , + {{0xe4, 0xe7, 0x43, 0x4b, 0xa0, 0x3f, 0x2b, 0x06, 0xba, 0x17, 0xae, 0x3d, 0xe6, 0xce, 0xbd, 0xb8, 0xed, 0x74, 0x11, 0x35, 0xec, 0x96, 0xfe, 0x31, 0xe3, 0x0e, 0x7a, 0x4e, 0xc9, 0x1d, 0xcb, 0x20}}}, +{{{0xe0, 0x67, 0xe9, 0x7b, 0xdb, 0x96, 0x5c, 0xb0, 0x32, 0xd0, 0x59, 0x31, 0x90, 0xdc, 0x92, 0x97, 0xac, 0x09, 0x38, 0x31, 0x0f, 0x7e, 0xd6, 0x5d, 0xd0, 0x06, 0xb6, 0x1f, 0xea, 0xf0, 0x5b, 0x07}} , + {{0x81, 0x9f, 0xc7, 0xde, 0x6b, 0x41, 0x22, 0x35, 0x14, 0x67, 0x77, 0x3e, 0x90, 0x81, 0xb0, 0xd9, 0x85, 0x4c, 0xca, 0x9b, 0x3f, 0x04, 0x59, 0xd6, 0xaa, 0x17, 0xc3, 0x88, 0x34, 0x37, 0xba, 0x43}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x4c, 0xb6, 0x69, 0xc8, 0x81, 0x95, 0x94, 0x33, 0x92, 0x34, 0xe9, 0x3c, 0x84, 0x0d, 0x3d, 0x5a, 0x37, 0x9c, 0x22, 0xa0, 0xaa, 0x65, 0xce, 0xb4, 0xc2, 0x2d, 0x66, 0x67, 0x02, 0xff, 0x74, 0x10}} , + {{0x22, 0xb0, 0xd5, 0xe6, 0xc7, 0xef, 0xb1, 0xa7, 0x13, 0xda, 0x60, 0xb4, 0x80, 0xc1, 0x42, 0x7d, 0x10, 0x70, 0x97, 0x04, 0x4d, 0xda, 0x23, 0x89, 0xc2, 0x0e, 0x68, 0xcb, 0xde, 0xe0, 0x9b, 0x29}}}, +{{{0x33, 0xfe, 0x42, 0x2a, 0x36, 0x2b, 0x2e, 0x36, 0x64, 0x5c, 0x8b, 0xcc, 0x81, 0x6a, 0x15, 0x08, 0xa1, 0x27, 0xe8, 0x57, 0xe5, 0x78, 0x8e, 0xf2, 0x58, 0x19, 0x12, 0x42, 0xae, 0xc4, 0x63, 0x3e}} , + {{0x78, 0x96, 0x9c, 0xa7, 0xca, 0x80, 0xae, 0x02, 0x85, 0xb1, 0x7c, 0x04, 0x5c, 0xc1, 0x5b, 0x26, 0xc1, 0xba, 0xed, 0xa5, 0x59, 0x70, 0x85, 0x8c, 0x8c, 0xe8, 0x87, 0xac, 0x6a, 0x28, 0x99, 0x35}}}, +{{{0x9f, 0x04, 0x08, 0x28, 0xbe, 0x87, 0xda, 0x80, 0x28, 0x38, 0xde, 0x9f, 0xcd, 0xe4, 0xe3, 0x62, 0xfb, 0x2e, 0x46, 0x8d, 0x01, 0xb3, 0x06, 0x51, 0xd4, 0x19, 0x3b, 0x11, 0xfa, 0xe2, 0xad, 0x1e}} , + {{0xa0, 0x20, 0x99, 0x69, 0x0a, 0xae, 0xa3, 0x70, 0x4e, 0x64, 0x80, 0xb7, 0x85, 0x9c, 0x87, 0x54, 0x43, 0x43, 0x55, 0x80, 0x6d, 0x8d, 0x7c, 0xa9, 0x64, 0xca, 0x6c, 0x2e, 0x21, 0xd8, 0xc8, 0x6c}}}, +{{{0x91, 0x4a, 0x07, 0xad, 0x08, 0x75, 0xc1, 0x4f, 0xa4, 0xb2, 0xc3, 0x6f, 0x46, 0x3e, 0xb1, 0xce, 0x52, 0xab, 0x67, 0x09, 0x54, 0x48, 0x6b, 0x6c, 0xd7, 0x1d, 0x71, 0x76, 0xcb, 0xff, 0xdd, 0x31}} , + {{0x36, 0x88, 0xfa, 0xfd, 0xf0, 0x36, 0x6f, 0x07, 0x74, 0x88, 0x50, 0xd0, 0x95, 0x38, 0x4a, 0x48, 0x2e, 0x07, 0x64, 0x97, 0x11, 0x76, 0x01, 0x1a, 0x27, 0x4d, 0x8e, 0x25, 0x9a, 0x9b, 0x1c, 0x22}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xbe, 0x57, 0xbd, 0x0e, 0x0f, 0xac, 0x5e, 0x76, 0xa3, 0x71, 0xad, 0x2b, 0x10, 0x45, 0x02, 0xec, 0x59, 0xd5, 0x5d, 0xa9, 0x44, 0xcc, 0x25, 0x4c, 0xb3, 0x3c, 0x5b, 0x69, 0x07, 0x55, 0x26, 0x6b}} , + {{0x30, 0x6b, 0xd4, 0xa7, 0x51, 0x29, 0xe3, 0xf9, 0x7a, 0x75, 0x2a, 0x82, 0x2f, 0xd6, 0x1d, 0x99, 0x2b, 0x80, 0xd5, 0x67, 0x1e, 0x15, 0x9d, 0xca, 0xfd, 0xeb, 0xac, 0x97, 0x35, 0x09, 0x7f, 0x3f}}}, +{{{0x35, 0x0d, 0x34, 0x0a, 0xb8, 0x67, 0x56, 0x29, 0x20, 0xf3, 0x19, 0x5f, 0xe2, 0x83, 0x42, 0x73, 0x53, 0xa8, 0xc5, 0x02, 0x19, 0x33, 0xb4, 0x64, 0xbd, 0xc3, 0x87, 0x8c, 0xd7, 0x76, 0xed, 0x25}} , + {{0x47, 0x39, 0x37, 0x76, 0x0d, 0x1d, 0x0c, 0xf5, 0x5a, 0x6d, 0x43, 0x88, 0x99, 0x15, 0xb4, 0x52, 0x0f, 0x2a, 0xb3, 0xb0, 0x3f, 0xa6, 0xb3, 0x26, 0xb3, 0xc7, 0x45, 0xf5, 0x92, 0x5f, 0x9b, 0x17}}}, +{{{0x9d, 0x23, 0xbd, 0x15, 0xfe, 0x52, 0x52, 0x15, 0x26, 0x79, 0x86, 0xba, 0x06, 0x56, 0x66, 0xbb, 0x8c, 0x2e, 0x10, 0x11, 0xd5, 0x4a, 0x18, 0x52, 0xda, 0x84, 0x44, 0xf0, 0x3e, 0xe9, 0x8c, 0x35}} , + {{0xad, 0xa0, 0x41, 0xec, 0xc8, 0x4d, 0xb9, 0xd2, 0x6e, 0x96, 0x4e, 0x5b, 0xc5, 0xc2, 0xa0, 0x1b, 0xcf, 0x0c, 0xbf, 0x17, 0x66, 0x57, 0xc1, 0x17, 0x90, 0x45, 0x71, 0xc2, 0xe1, 0x24, 0xeb, 0x27}}}, +{{{0x2c, 0xb9, 0x42, 0xa4, 0xaf, 0x3b, 0x42, 0x0e, 0xc2, 0x0f, 0xf2, 0xea, 0x83, 0xaf, 0x9a, 0x13, 0x17, 0xb0, 0xbd, 0x89, 0x17, 0xe3, 0x72, 0xcb, 0x0e, 0x76, 0x7e, 0x41, 0x63, 0x04, 0x88, 0x71}} , + {{0x75, 0x78, 0x38, 0x86, 0x57, 0xdd, 0x9f, 0xee, 0x54, 0x70, 0x65, 0xbf, 0xf1, 0x2c, 0xe0, 0x39, 0x0d, 0xe3, 0x89, 0xfd, 0x8e, 0x93, 0x4f, 0x43, 0xdc, 0xd5, 0x5b, 0xde, 0xf9, 0x98, 0xe5, 0x7b}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xe7, 0x3b, 0x65, 0x11, 0xdf, 0xb2, 0xf2, 0x63, 0x94, 0x12, 0x6f, 0x5c, 0x9e, 0x77, 0xc1, 0xb6, 0xd8, 0xab, 0x58, 0x7a, 0x1d, 0x95, 0x73, 0xdd, 0xe7, 0xe3, 0x6f, 0xf2, 0x03, 0x1d, 0xdb, 0x76}} , + {{0xae, 0x06, 0x4e, 0x2c, 0x52, 0x1b, 0xbc, 0x5a, 0x5a, 0xa5, 0xbe, 0x27, 0xbd, 0xeb, 0xe1, 0x14, 0x17, 0x68, 0x26, 0x07, 0x03, 0xd1, 0x18, 0x0b, 0xdf, 0xf1, 0x06, 0x5c, 0xa6, 0x1b, 0xb9, 0x24}}}, +{{{0xc5, 0x66, 0x80, 0x13, 0x0e, 0x48, 0x8c, 0x87, 0x31, 0x84, 0xb4, 0x60, 0xed, 0xc5, 0xec, 0xb6, 0xc5, 0x05, 0x33, 0x5f, 0x2f, 0x7d, 0x40, 0xb6, 0x32, 0x1d, 0x38, 0x74, 0x1b, 0xf1, 0x09, 0x3d}} , + {{0xd4, 0x69, 0x82, 0xbc, 0x8d, 0xf8, 0x34, 0x36, 0x75, 0x55, 0x18, 0x55, 0x58, 0x3c, 0x79, 0xaf, 0x26, 0x80, 0xab, 0x9b, 0x95, 0x00, 0xf1, 0xcb, 0xda, 0xc1, 0x9f, 0xf6, 0x2f, 0xa2, 0xf4, 0x45}}}, +{{{0x17, 0xbe, 0xeb, 0x85, 0xed, 0x9e, 0xcd, 0x56, 0xf5, 0x17, 0x45, 0x42, 0xb4, 0x1f, 0x44, 0x4c, 0x05, 0x74, 0x15, 0x47, 0x00, 0xc6, 0x6a, 0x3d, 0x24, 0x09, 0x0d, 0x58, 0xb1, 0x42, 0xd7, 0x04}} , + {{0x8d, 0xbd, 0xa3, 0xc4, 0x06, 0x9b, 0x1f, 0x90, 0x58, 0x60, 0x74, 0xb2, 0x00, 0x3b, 0x3c, 0xd2, 0xda, 0x82, 0xbb, 0x10, 0x90, 0x69, 0x92, 0xa9, 0xb4, 0x30, 0x81, 0xe3, 0x7c, 0xa8, 0x89, 0x45}}}, +{{{0x3f, 0xdc, 0x05, 0xcb, 0x41, 0x3c, 0xc8, 0x23, 0x04, 0x2c, 0x38, 0x99, 0xe3, 0x68, 0x55, 0xf9, 0xd3, 0x32, 0xc7, 0xbf, 0xfa, 0xd4, 0x1b, 0x5d, 0xde, 0xdc, 0x10, 0x42, 0xc0, 0x42, 0xd9, 0x75}} , + {{0x2d, 0xab, 0x35, 0x4e, 0x87, 0xc4, 0x65, 0x97, 0x67, 0x24, 0xa4, 0x47, 0xad, 0x3f, 0x8e, 0xf3, 0xcb, 0x31, 0x17, 0x77, 0xc5, 0xe2, 0xd7, 0x8f, 0x3c, 0xc1, 0xcd, 0x56, 0x48, 0xc1, 0x6c, 0x69}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x14, 0xae, 0x5f, 0x88, 0x7b, 0xa5, 0x90, 0xdf, 0x10, 0xb2, 0x8b, 0x5e, 0x24, 0x17, 0xc3, 0xa3, 0xd4, 0x0f, 0x92, 0x61, 0x1a, 0x19, 0x5a, 0xad, 0x76, 0xbd, 0xd8, 0x1c, 0xdd, 0xe0, 0x12, 0x6d}} , + {{0x8e, 0xbd, 0x70, 0x8f, 0x02, 0xa3, 0x24, 0x4d, 0x5a, 0x67, 0xc4, 0xda, 0xf7, 0x20, 0x0f, 0x81, 0x5b, 0x7a, 0x05, 0x24, 0x67, 0x83, 0x0b, 0x2a, 0x80, 0xe7, 0xfd, 0x74, 0x4b, 0x9e, 0x5c, 0x0d}}}, +{{{0x94, 0xd5, 0x5f, 0x1f, 0xa2, 0xfb, 0xeb, 0xe1, 0x07, 0x34, 0xf8, 0x20, 0xad, 0x81, 0x30, 0x06, 0x2d, 0xa1, 0x81, 0x95, 0x36, 0xcf, 0x11, 0x0b, 0xaf, 0xc1, 0x2b, 0x9a, 0x6c, 0x55, 0xc1, 0x16}} , + {{0x36, 0x4f, 0xf1, 0x5e, 0x74, 0x35, 0x13, 0x28, 0xd7, 0x11, 0xcf, 0xb8, 0xde, 0x93, 0xb3, 0x05, 0xb8, 0xb5, 0x73, 0xe9, 0xeb, 0xad, 0x19, 0x1e, 0x89, 0x0f, 0x8b, 0x15, 0xd5, 0x8c, 0xe3, 0x23}}}, +{{{0x33, 0x79, 0xe7, 0x18, 0xe6, 0x0f, 0x57, 0x93, 0x15, 0xa0, 0xa7, 0xaa, 0xc4, 0xbf, 0x4f, 0x30, 0x74, 0x95, 0x5e, 0x69, 0x4a, 0x5b, 0x45, 0xe4, 0x00, 0xeb, 0x23, 0x74, 0x4c, 0xdf, 0x6b, 0x45}} , + {{0x97, 0x29, 0x6c, 0xc4, 0x42, 0x0b, 0xdd, 0xc0, 0x29, 0x5c, 0x9b, 0x34, 0x97, 0xd0, 0xc7, 0x79, 0x80, 0x63, 0x74, 0xe4, 0x8e, 0x37, 0xb0, 0x2b, 0x7c, 0xe8, 0x68, 0x6c, 0xc3, 0x82, 0x97, 0x57}}}, +{{{0x22, 0xbe, 0x83, 0xb6, 0x4b, 0x80, 0x6b, 0x43, 0x24, 0x5e, 0xef, 0x99, 0x9b, 0xa8, 0xfc, 0x25, 0x8d, 0x3b, 0x03, 0x94, 0x2b, 0x3e, 0xe7, 0x95, 0x76, 0x9b, 0xcc, 0x15, 0xdb, 0x32, 0xe6, 0x66}} , + {{0x84, 0xf0, 0x4a, 0x13, 0xa6, 0xd6, 0xfa, 0x93, 0x46, 0x07, 0xf6, 0x7e, 0x5c, 0x6d, 0x5e, 0xf6, 0xa6, 0xe7, 0x48, 0xf0, 0x06, 0xea, 0xff, 0x90, 0xc1, 0xcc, 0x4c, 0x19, 0x9c, 0x3c, 0x4e, 0x53}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2a, 0x50, 0xe3, 0x07, 0x15, 0x59, 0xf2, 0x8b, 0x81, 0xf2, 0xf3, 0xd3, 0x6c, 0x99, 0x8c, 0x70, 0x67, 0xec, 0xcc, 0xee, 0x9e, 0x59, 0x45, 0x59, 0x7d, 0x47, 0x75, 0x69, 0xf5, 0x24, 0x93, 0x5d}} , + {{0x6a, 0x4f, 0x1b, 0xbe, 0x6b, 0x30, 0xcf, 0x75, 0x46, 0xe3, 0x7b, 0x9d, 0xfc, 0xcd, 0xd8, 0x5c, 0x1f, 0xb4, 0xc8, 0xe2, 0x24, 0xec, 0x1a, 0x28, 0x05, 0x32, 0x57, 0xfd, 0x3c, 0x5a, 0x98, 0x10}}}, +{{{0xa3, 0xdb, 0xf7, 0x30, 0xd8, 0xc2, 0x9a, 0xe1, 0xd3, 0xce, 0x22, 0xe5, 0x80, 0x1e, 0xd9, 0xe4, 0x1f, 0xab, 0xc0, 0x71, 0x1a, 0x86, 0x0e, 0x27, 0x99, 0x5b, 0xfa, 0x76, 0x99, 0xb0, 0x08, 0x3c}} , + {{0x2a, 0x93, 0xd2, 0x85, 0x1b, 0x6a, 0x5d, 0xa6, 0xee, 0xd1, 0xd1, 0x33, 0xbd, 0x6a, 0x36, 0x73, 0x37, 0x3a, 0x44, 0xb4, 0xec, 0xa9, 0x7a, 0xde, 0x83, 0x40, 0xd7, 0xdf, 0x28, 0xba, 0xa2, 0x30}}}, +{{{0xd3, 0xb5, 0x6d, 0x05, 0x3f, 0x9f, 0xf3, 0x15, 0x8d, 0x7c, 0xca, 0xc9, 0xfc, 0x8a, 0x7c, 0x94, 0xb0, 0x63, 0x36, 0x9b, 0x78, 0xd1, 0x91, 0x1f, 0x93, 0xd8, 0x57, 0x43, 0xde, 0x76, 0xa3, 0x43}} , + {{0x9b, 0x35, 0xe2, 0xa9, 0x3d, 0x32, 0x1e, 0xbb, 0x16, 0x28, 0x70, 0xe9, 0x45, 0x2f, 0x8f, 0x70, 0x7f, 0x08, 0x7e, 0x53, 0xc4, 0x7a, 0xbf, 0xf7, 0xe1, 0xa4, 0x6a, 0xd8, 0xac, 0x64, 0x1b, 0x11}}}, +{{{0xb2, 0xeb, 0x47, 0x46, 0x18, 0x3e, 0x1f, 0x99, 0x0c, 0xcc, 0xf1, 0x2c, 0xe0, 0xe7, 0x8f, 0xe0, 0x01, 0x7e, 0x65, 0xb8, 0x0c, 0xd0, 0xfb, 0xc8, 0xb9, 0x90, 0x98, 0x33, 0x61, 0x3b, 0xd8, 0x27}} , + {{0xa0, 0xbe, 0x72, 0x3a, 0x50, 0x4b, 0x74, 0xab, 0x01, 0xc8, 0x93, 0xc5, 0xe4, 0xc7, 0x08, 0x6c, 0xb4, 0xca, 0xee, 0xeb, 0x8e, 0xd7, 0x4e, 0x26, 0xc6, 0x1d, 0xe2, 0x71, 0xaf, 0x89, 0xa0, 0x2a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x98, 0x0b, 0xe4, 0xde, 0xdb, 0xa8, 0xfa, 0x82, 0x74, 0x06, 0x52, 0x6d, 0x08, 0x52, 0x8a, 0xff, 0x62, 0xc5, 0x6a, 0x44, 0x0f, 0x51, 0x8c, 0x1f, 0x6e, 0xb6, 0xc6, 0x2c, 0x81, 0xd3, 0x76, 0x46}} , + {{0xf4, 0x29, 0x74, 0x2e, 0x80, 0xa7, 0x1a, 0x8f, 0xf6, 0xbd, 0xd6, 0x8e, 0xbf, 0xc1, 0x95, 0x2a, 0xeb, 0xa0, 0x7f, 0x45, 0xa0, 0x50, 0x14, 0x05, 0xb1, 0x57, 0x4c, 0x74, 0xb7, 0xe2, 0x89, 0x7d}}}, +{{{0x07, 0xee, 0xa7, 0xad, 0xb7, 0x09, 0x0b, 0x49, 0x4e, 0xbf, 0xca, 0xe5, 0x21, 0xe6, 0xe6, 0xaf, 0xd5, 0x67, 0xf3, 0xce, 0x7e, 0x7c, 0x93, 0x7b, 0x5a, 0x10, 0x12, 0x0e, 0x6c, 0x06, 0x11, 0x75}} , + {{0xd5, 0xfc, 0x86, 0xa3, 0x3b, 0xa3, 0x3e, 0x0a, 0xfb, 0x0b, 0xf7, 0x36, 0xb1, 0x5b, 0xda, 0x70, 0xb7, 0x00, 0xa7, 0xda, 0x88, 0x8f, 0x84, 0xa8, 0xbc, 0x1c, 0x39, 0xb8, 0x65, 0xf3, 0x4d, 0x60}}}, +{{{0x96, 0x9d, 0x31, 0xf4, 0xa2, 0xbe, 0x81, 0xb9, 0xa5, 0x59, 0x9e, 0xba, 0x07, 0xbe, 0x74, 0x58, 0xd8, 0xeb, 0xc5, 0x9f, 0x3d, 0xd1, 0xf4, 0xae, 0xce, 0x53, 0xdf, 0x4f, 0xc7, 0x2a, 0x89, 0x4d}} , + {{0x29, 0xd8, 0xf2, 0xaa, 0xe9, 0x0e, 0xf7, 0x2e, 0x5f, 0x9d, 0x8a, 0x5b, 0x09, 0xed, 0xc9, 0x24, 0x22, 0xf4, 0x0f, 0x25, 0x8f, 0x1c, 0x84, 0x6e, 0x34, 0x14, 0x6c, 0xea, 0xb3, 0x86, 0x5d, 0x04}}}, +{{{0x07, 0x98, 0x61, 0xe8, 0x6a, 0xd2, 0x81, 0x49, 0x25, 0xd5, 0x5b, 0x18, 0xc7, 0x35, 0x52, 0x51, 0xa4, 0x46, 0xad, 0x18, 0x0d, 0xc9, 0x5f, 0x18, 0x91, 0x3b, 0xb4, 0xc0, 0x60, 0x59, 0x8d, 0x66}} , + {{0x03, 0x1b, 0x79, 0x53, 0x6e, 0x24, 0xae, 0x57, 0xd9, 0x58, 0x09, 0x85, 0x48, 0xa2, 0xd3, 0xb5, 0xe2, 0x4d, 0x11, 0x82, 0xe6, 0x86, 0x3c, 0xe9, 0xb1, 0x00, 0x19, 0xc2, 0x57, 0xf7, 0x66, 0x7a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x0f, 0xe3, 0x89, 0x03, 0xd7, 0x22, 0x95, 0x9f, 0xca, 0xb4, 0x8d, 0x9e, 0x6d, 0x97, 0xff, 0x8d, 0x21, 0x59, 0x07, 0xef, 0x03, 0x2d, 0x5e, 0xf8, 0x44, 0x46, 0xe7, 0x85, 0x80, 0xc5, 0x89, 0x50}} , + {{0x8b, 0xd8, 0x53, 0x86, 0x24, 0x86, 0x29, 0x52, 0x01, 0xfa, 0x20, 0xc3, 0x4e, 0x95, 0xcb, 0xad, 0x7b, 0x34, 0x94, 0x30, 0xb7, 0x7a, 0xfa, 0x96, 0x41, 0x60, 0x2b, 0xcb, 0x59, 0xb9, 0xca, 0x50}}}, +{{{0xc2, 0x5b, 0x9b, 0x78, 0x23, 0x1b, 0x3a, 0x88, 0x94, 0x5f, 0x0a, 0x9b, 0x98, 0x2b, 0x6e, 0x53, 0x11, 0xf6, 0xff, 0xc6, 0x7d, 0x42, 0xcc, 0x02, 0x80, 0x40, 0x0d, 0x1e, 0xfb, 0xaf, 0x61, 0x07}} , + {{0xb0, 0xe6, 0x2f, 0x81, 0x70, 0xa1, 0x2e, 0x39, 0x04, 0x7c, 0xc4, 0x2c, 0x87, 0x45, 0x4a, 0x5b, 0x69, 0x97, 0xac, 0x6d, 0x2c, 0x10, 0x42, 0x7c, 0x3b, 0x15, 0x70, 0x60, 0x0e, 0x11, 0x6d, 0x3a}}}, +{{{0x9b, 0x18, 0x80, 0x5e, 0xdb, 0x05, 0xbd, 0xc6, 0xb7, 0x3c, 0xc2, 0x40, 0x4d, 0x5d, 0xce, 0x97, 0x8a, 0x34, 0x15, 0xab, 0x28, 0x5d, 0x10, 0xf0, 0x37, 0x0c, 0xcc, 0x16, 0xfa, 0x1f, 0x33, 0x0d}} , + {{0x19, 0xf9, 0x35, 0xaa, 0x59, 0x1a, 0x0c, 0x5c, 0x06, 0xfc, 0x6a, 0x0b, 0x97, 0x53, 0x36, 0xfc, 0x2a, 0xa5, 0x5a, 0x9b, 0x30, 0xef, 0x23, 0xaf, 0x39, 0x5d, 0x9a, 0x6b, 0x75, 0x57, 0x48, 0x0b}}}, +{{{0x26, 0xdc, 0x76, 0x3b, 0xfc, 0xf9, 0x9c, 0x3f, 0x89, 0x0b, 0x62, 0x53, 0xaf, 0x83, 0x01, 0x2e, 0xbc, 0x6a, 0xc6, 0x03, 0x0d, 0x75, 0x2a, 0x0d, 0xe6, 0x94, 0x54, 0xcf, 0xb3, 0xe5, 0x96, 0x25}} , + {{0xfe, 0x82, 0xb1, 0x74, 0x31, 0x8a, 0xa7, 0x6f, 0x56, 0xbd, 0x8d, 0xf4, 0xe0, 0x94, 0x51, 0x59, 0xde, 0x2c, 0x5a, 0xf4, 0x84, 0x6b, 0x4a, 0x88, 0x93, 0xc0, 0x0c, 0x9a, 0xac, 0xa7, 0xa0, 0x68}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x25, 0x0d, 0xd6, 0xc7, 0x23, 0x47, 0x10, 0xad, 0xc7, 0x08, 0x5c, 0x87, 0x87, 0x93, 0x98, 0x18, 0xb8, 0xd3, 0x9c, 0xac, 0x5a, 0x3d, 0xc5, 0x75, 0xf8, 0x49, 0x32, 0x14, 0xcc, 0x51, 0x96, 0x24}} , + {{0x65, 0x9c, 0x5d, 0xf0, 0x37, 0x04, 0xf0, 0x34, 0x69, 0x2a, 0xf0, 0xa5, 0x64, 0xca, 0xde, 0x2b, 0x5b, 0x15, 0x10, 0xd2, 0xab, 0x06, 0xdd, 0xc4, 0xb0, 0xb6, 0x5b, 0xc1, 0x17, 0xdf, 0x8f, 0x02}}}, +{{{0xbd, 0x59, 0x3d, 0xbf, 0x5c, 0x31, 0x44, 0x2c, 0x32, 0x94, 0x04, 0x60, 0x84, 0x0f, 0xad, 0x00, 0xb6, 0x8f, 0xc9, 0x1d, 0xcc, 0x5c, 0xa2, 0x49, 0x0e, 0x50, 0x91, 0x08, 0x9a, 0x43, 0x55, 0x05}} , + {{0x5d, 0x93, 0x55, 0xdf, 0x9b, 0x12, 0x19, 0xec, 0x93, 0x85, 0x42, 0x9e, 0x66, 0x0f, 0x9d, 0xaf, 0x99, 0xaf, 0x26, 0x89, 0xbc, 0x61, 0xfd, 0xff, 0xce, 0x4b, 0xf4, 0x33, 0x95, 0xc9, 0x35, 0x58}}}, +{{{0x12, 0x55, 0xf9, 0xda, 0xcb, 0x44, 0xa7, 0xdc, 0x57, 0xe2, 0xf9, 0x9a, 0xe6, 0x07, 0x23, 0x60, 0x54, 0xa7, 0x39, 0xa5, 0x9b, 0x84, 0x56, 0x6e, 0xaa, 0x8b, 0x8f, 0xb0, 0x2c, 0x87, 0xaf, 0x67}} , + {{0x00, 0xa9, 0x4c, 0xb2, 0x12, 0xf8, 0x32, 0xa8, 0x7a, 0x00, 0x4b, 0x49, 0x32, 0xba, 0x1f, 0x5d, 0x44, 0x8e, 0x44, 0x7a, 0xdc, 0x11, 0xfb, 0x39, 0x08, 0x57, 0x87, 0xa5, 0x12, 0x42, 0x93, 0x0e}}}, +{{{0x17, 0xb4, 0xae, 0x72, 0x59, 0xd0, 0xaa, 0xa8, 0x16, 0x8b, 0x63, 0x11, 0xb3, 0x43, 0x04, 0xda, 0x0c, 0xa8, 0xb7, 0x68, 0xdd, 0x4e, 0x54, 0xe7, 0xaf, 0x5d, 0x5d, 0x05, 0x76, 0x36, 0xec, 0x0d}} , + {{0x6d, 0x7c, 0x82, 0x32, 0x38, 0x55, 0x57, 0x74, 0x5b, 0x7d, 0xc3, 0xc4, 0xfb, 0x06, 0x29, 0xf0, 0x13, 0x55, 0x54, 0xc6, 0xa7, 0xdc, 0x4c, 0x9f, 0x98, 0x49, 0x20, 0xa8, 0xc3, 0x8d, 0xfa, 0x48}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x87, 0x47, 0x9d, 0xe9, 0x25, 0xd5, 0xe3, 0x47, 0x78, 0xdf, 0x85, 0xa7, 0x85, 0x5e, 0x7a, 0x4c, 0x5f, 0x79, 0x1a, 0xf3, 0xa2, 0xb2, 0x28, 0xa0, 0x9c, 0xdd, 0x30, 0x40, 0xd4, 0x38, 0xbd, 0x28}} , + {{0xfc, 0xbb, 0xd5, 0x78, 0x6d, 0x1d, 0xd4, 0x99, 0xb4, 0xaa, 0x44, 0x44, 0x7a, 0x1b, 0xd8, 0xfe, 0xb4, 0x99, 0xb9, 0xcc, 0xe7, 0xc4, 0xd3, 0x3a, 0x73, 0x83, 0x41, 0x5c, 0x40, 0xd7, 0x2d, 0x55}}}, +{{{0x26, 0xe1, 0x7b, 0x5f, 0xe5, 0xdc, 0x3f, 0x7d, 0xa1, 0xa7, 0x26, 0x44, 0x22, 0x23, 0xc0, 0x8f, 0x7d, 0xf1, 0xb5, 0x11, 0x47, 0x7b, 0x19, 0xd4, 0x75, 0x6f, 0x1e, 0xa5, 0x27, 0xfe, 0xc8, 0x0e}} , + {{0xd3, 0x11, 0x3d, 0xab, 0xef, 0x2c, 0xed, 0xb1, 0x3d, 0x7c, 0x32, 0x81, 0x6b, 0xfe, 0xf8, 0x1c, 0x3c, 0x7b, 0xc0, 0x61, 0xdf, 0xb8, 0x75, 0x76, 0x7f, 0xaa, 0xd8, 0x93, 0xaf, 0x3d, 0xe8, 0x3d}}}, +{{{0xfd, 0x5b, 0x4e, 0x8d, 0xb6, 0x7e, 0x82, 0x9b, 0xef, 0xce, 0x04, 0x69, 0x51, 0x52, 0xff, 0xef, 0xa0, 0x52, 0xb5, 0x79, 0x17, 0x5e, 0x2f, 0xde, 0xd6, 0x3c, 0x2d, 0xa0, 0x43, 0xb4, 0x0b, 0x19}} , + {{0xc0, 0x61, 0x48, 0x48, 0x17, 0xf4, 0x9e, 0x18, 0x51, 0x2d, 0xea, 0x2f, 0xf2, 0xf2, 0xe0, 0xa3, 0x14, 0xb7, 0x8b, 0x3a, 0x30, 0xf5, 0x81, 0xc1, 0x5d, 0x71, 0x39, 0x62, 0x55, 0x1f, 0x60, 0x5a}}}, +{{{0xe5, 0x89, 0x8a, 0x76, 0x6c, 0xdb, 0x4d, 0x0a, 0x5b, 0x72, 0x9d, 0x59, 0x6e, 0x63, 0x63, 0x18, 0x7c, 0xe3, 0xfa, 0xe2, 0xdb, 0xa1, 0x8d, 0xf4, 0xa5, 0xd7, 0x16, 0xb2, 0xd0, 0xb3, 0x3f, 0x39}} , + {{0xce, 0x60, 0x09, 0x6c, 0xf5, 0x76, 0x17, 0x24, 0x80, 0x3a, 0x96, 0xc7, 0x94, 0x2e, 0xf7, 0x6b, 0xef, 0xb5, 0x05, 0x96, 0xef, 0xd3, 0x7b, 0x51, 0xda, 0x05, 0x44, 0x67, 0xbc, 0x07, 0x21, 0x4e}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xe9, 0x73, 0x6f, 0x21, 0xb9, 0xde, 0x22, 0x7d, 0xeb, 0x97, 0x31, 0x10, 0xa3, 0xea, 0xe1, 0xc6, 0x37, 0xeb, 0x8f, 0x43, 0x58, 0xde, 0x41, 0x64, 0x0e, 0x3e, 0x07, 0x99, 0x3d, 0xf1, 0xdf, 0x1e}} , + {{0xf8, 0xad, 0x43, 0xc2, 0x17, 0x06, 0xe2, 0xe4, 0xa9, 0x86, 0xcd, 0x18, 0xd7, 0x78, 0xc8, 0x74, 0x66, 0xd2, 0x09, 0x18, 0xa5, 0xf1, 0xca, 0xa6, 0x62, 0x92, 0xc1, 0xcb, 0x00, 0xeb, 0x42, 0x2e}}}, +{{{0x7b, 0x34, 0x24, 0x4c, 0xcf, 0x38, 0xe5, 0x6c, 0x0a, 0x01, 0x2c, 0x22, 0x0b, 0x24, 0x38, 0xad, 0x24, 0x7e, 0x19, 0xf0, 0x6c, 0xf9, 0x31, 0xf4, 0x35, 0x11, 0xf6, 0x46, 0x33, 0x3a, 0x23, 0x59}} , + {{0x20, 0x0b, 0xa1, 0x08, 0x19, 0xad, 0x39, 0x54, 0xea, 0x3e, 0x23, 0x09, 0xb6, 0xe2, 0xd2, 0xbc, 0x4d, 0xfc, 0x9c, 0xf0, 0x13, 0x16, 0x22, 0x3f, 0xb9, 0xd2, 0x11, 0x86, 0x90, 0x55, 0xce, 0x3c}}}, +{{{0xc4, 0x0b, 0x4b, 0x62, 0x99, 0x37, 0x84, 0x3f, 0x74, 0xa2, 0xf9, 0xce, 0xe2, 0x0b, 0x0f, 0x2a, 0x3d, 0xa3, 0xe3, 0xdb, 0x5a, 0x9d, 0x93, 0xcc, 0xa5, 0xef, 0x82, 0x91, 0x1d, 0xe6, 0x6c, 0x68}} , + {{0xa3, 0x64, 0x17, 0x9b, 0x8b, 0xc8, 0x3a, 0x61, 0xe6, 0x9d, 0xc6, 0xed, 0x7b, 0x03, 0x52, 0x26, 0x9d, 0x3a, 0xb3, 0x13, 0xcc, 0x8a, 0xfd, 0x2c, 0x1a, 0x1d, 0xed, 0x13, 0xd0, 0x55, 0x57, 0x0e}}}, +{{{0x1a, 0xea, 0xbf, 0xfd, 0x4a, 0x3c, 0x8e, 0xec, 0x29, 0x7e, 0x77, 0x77, 0x12, 0x99, 0xd7, 0x84, 0xf9, 0x55, 0x7f, 0xf1, 0x8b, 0xb4, 0xd2, 0x95, 0xa3, 0x8d, 0xf0, 0x8a, 0xa7, 0xeb, 0x82, 0x4b}} , + {{0x2c, 0x28, 0xf4, 0x3a, 0xf6, 0xde, 0x0a, 0xe0, 0x41, 0x44, 0x23, 0xf8, 0x3f, 0x03, 0x64, 0x9f, 0xc3, 0x55, 0x4c, 0xc6, 0xc1, 0x94, 0x1c, 0x24, 0x5d, 0x5f, 0x92, 0x45, 0x96, 0x57, 0x37, 0x14}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xc1, 0xcd, 0x90, 0x66, 0xb9, 0x76, 0xa0, 0x5b, 0xa5, 0x85, 0x75, 0x23, 0xf9, 0x89, 0xa5, 0x82, 0xb2, 0x6f, 0xb1, 0xeb, 0xc4, 0x69, 0x6f, 0x18, 0x5a, 0xed, 0x94, 0x3d, 0x9d, 0xd9, 0x2c, 0x1a}} , + {{0x35, 0xb0, 0xe6, 0x73, 0x06, 0xb7, 0x37, 0xe0, 0xf8, 0xb0, 0x22, 0xe8, 0xd2, 0xed, 0x0b, 0xef, 0xe6, 0xc6, 0x5a, 0x99, 0x9e, 0x1a, 0x9f, 0x04, 0x97, 0xe4, 0x4d, 0x0b, 0xbe, 0xba, 0x44, 0x40}}}, +{{{0xc1, 0x56, 0x96, 0x91, 0x5f, 0x1f, 0xbb, 0x54, 0x6f, 0x88, 0x89, 0x0a, 0xb2, 0xd6, 0x41, 0x42, 0x6a, 0x82, 0xee, 0x14, 0xaa, 0x76, 0x30, 0x65, 0x0f, 0x67, 0x39, 0xa6, 0x51, 0x7c, 0x49, 0x24}} , + {{0x35, 0xa3, 0x78, 0xd1, 0x11, 0x0f, 0x75, 0xd3, 0x70, 0x46, 0xdb, 0x20, 0x51, 0xcb, 0x92, 0x80, 0x54, 0x10, 0x74, 0x36, 0x86, 0xa9, 0xd7, 0xa3, 0x08, 0x78, 0xf1, 0x01, 0x29, 0xf8, 0x80, 0x3b}}}, +{{{0xdb, 0xa7, 0x9d, 0x9d, 0xbf, 0xa0, 0xcc, 0xed, 0x53, 0xa2, 0xa2, 0x19, 0x39, 0x48, 0x83, 0x19, 0x37, 0x58, 0xd1, 0x04, 0x28, 0x40, 0xf7, 0x8a, 0xc2, 0x08, 0xb7, 0xa5, 0x42, 0xcf, 0x53, 0x4c}} , + {{0xa7, 0xbb, 0xf6, 0x8e, 0xad, 0xdd, 0xf7, 0x90, 0xdd, 0x5f, 0x93, 0x89, 0xae, 0x04, 0x37, 0xe6, 0x9a, 0xb7, 0xe8, 0xc0, 0xdf, 0x16, 0x2a, 0xbf, 0xc4, 0x3a, 0x3c, 0x41, 0xd5, 0x89, 0x72, 0x5a}}}, +{{{0x1f, 0x96, 0xff, 0x34, 0x2c, 0x13, 0x21, 0xcb, 0x0a, 0x89, 0x85, 0xbe, 0xb3, 0x70, 0x9e, 0x1e, 0xde, 0x97, 0xaf, 0x96, 0x30, 0xf7, 0x48, 0x89, 0x40, 0x8d, 0x07, 0xf1, 0x25, 0xf0, 0x30, 0x58}} , + {{0x1e, 0xd4, 0x93, 0x57, 0xe2, 0x17, 0xe7, 0x9d, 0xab, 0x3c, 0x55, 0x03, 0x82, 0x2f, 0x2b, 0xdb, 0x56, 0x1e, 0x30, 0x2e, 0x24, 0x47, 0x6e, 0xe6, 0xff, 0x33, 0x24, 0x2c, 0x75, 0x51, 0xd4, 0x67}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2b, 0x06, 0xd9, 0xa1, 0x5d, 0xe1, 0xf4, 0xd1, 0x1e, 0x3c, 0x9a, 0xc6, 0x29, 0x2b, 0x13, 0x13, 0x78, 0xc0, 0xd8, 0x16, 0x17, 0x2d, 0x9e, 0xa9, 0xc9, 0x79, 0x57, 0xab, 0x24, 0x91, 0x92, 0x19}} , + {{0x69, 0xfb, 0xa1, 0x9c, 0xa6, 0x75, 0x49, 0x7d, 0x60, 0x73, 0x40, 0x42, 0xc4, 0x13, 0x0a, 0x95, 0x79, 0x1e, 0x04, 0x83, 0x94, 0x99, 0x9b, 0x1e, 0x0c, 0xe8, 0x1f, 0x54, 0xef, 0xcb, 0xc0, 0x52}}}, +{{{0x14, 0x89, 0x73, 0xa1, 0x37, 0x87, 0x6a, 0x7a, 0xcf, 0x1d, 0xd9, 0x2e, 0x1a, 0x67, 0xed, 0x74, 0xc0, 0xf0, 0x9c, 0x33, 0xdd, 0xdf, 0x08, 0xbf, 0x7b, 0xd1, 0x66, 0xda, 0xe6, 0xc9, 0x49, 0x08}} , + {{0xe9, 0xdd, 0x5e, 0x55, 0xb0, 0x0a, 0xde, 0x21, 0x4c, 0x5a, 0x2e, 0xd4, 0x80, 0x3a, 0x57, 0x92, 0x7a, 0xf1, 0xc4, 0x2c, 0x40, 0xaf, 0x2f, 0xc9, 0x92, 0x03, 0xe5, 0x5a, 0xbc, 0xdc, 0xf4, 0x09}}}, +{{{0xf3, 0xe1, 0x2b, 0x7c, 0x05, 0x86, 0x80, 0x93, 0x4a, 0xad, 0xb4, 0x8f, 0x7e, 0x99, 0x0c, 0xfd, 0xcd, 0xef, 0xd1, 0xff, 0x2c, 0x69, 0x34, 0x13, 0x41, 0x64, 0xcf, 0x3b, 0xd0, 0x90, 0x09, 0x1e}} , + {{0x9d, 0x45, 0xd6, 0x80, 0xe6, 0x45, 0xaa, 0xf4, 0x15, 0xaa, 0x5c, 0x34, 0x87, 0x99, 0xa2, 0x8c, 0x26, 0x84, 0x62, 0x7d, 0xb6, 0x29, 0xc0, 0x52, 0xea, 0xf5, 0x81, 0x18, 0x0f, 0x35, 0xa9, 0x0e}}}, +{{{0xe7, 0x20, 0x72, 0x7c, 0x6d, 0x94, 0x5f, 0x52, 0x44, 0x54, 0xe3, 0xf1, 0xb2, 0xb0, 0x36, 0x46, 0x0f, 0xae, 0x92, 0xe8, 0x70, 0x9d, 0x6e, 0x79, 0xb1, 0xad, 0x37, 0xa9, 0x5f, 0xc0, 0xde, 0x03}} , + {{0x15, 0x55, 0x37, 0xc6, 0x1c, 0x27, 0x1c, 0x6d, 0x14, 0x4f, 0xca, 0xa4, 0xc4, 0x88, 0x25, 0x46, 0x39, 0xfc, 0x5a, 0xe5, 0xfe, 0x29, 0x11, 0x69, 0xf5, 0x72, 0x84, 0x4d, 0x78, 0x9f, 0x94, 0x15}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xec, 0xd3, 0xff, 0x57, 0x0b, 0xb0, 0xb2, 0xdc, 0xf8, 0x4f, 0xe2, 0x12, 0xd5, 0x36, 0xbe, 0x6b, 0x09, 0x43, 0x6d, 0xa3, 0x4d, 0x90, 0x2d, 0xb8, 0x74, 0xe8, 0x71, 0x45, 0x19, 0x8b, 0x0c, 0x6a}} , + {{0xb8, 0x42, 0x1c, 0x03, 0xad, 0x2c, 0x03, 0x8e, 0xac, 0xd7, 0x98, 0x29, 0x13, 0xc6, 0x02, 0x29, 0xb5, 0xd4, 0xe7, 0xcf, 0xcc, 0x8b, 0x83, 0xec, 0x35, 0xc7, 0x9c, 0x74, 0xb7, 0xad, 0x85, 0x5f}}}, +{{{0x78, 0x84, 0xe1, 0x56, 0x45, 0x69, 0x68, 0x5a, 0x4f, 0xb8, 0xb1, 0x29, 0xff, 0x33, 0x03, 0x31, 0xb7, 0xcb, 0x96, 0x25, 0xe6, 0xe6, 0x41, 0x98, 0x1a, 0xbb, 0x03, 0x56, 0xf2, 0xb2, 0x91, 0x34}} , + {{0x2c, 0x6c, 0xf7, 0x66, 0xa4, 0x62, 0x6b, 0x39, 0xb3, 0xba, 0x65, 0xd3, 0x1c, 0xf8, 0x11, 0xaa, 0xbe, 0xdc, 0x80, 0x59, 0x87, 0xf5, 0x7b, 0xe5, 0xe3, 0xb3, 0x3e, 0x39, 0xda, 0xbe, 0x88, 0x09}}}, +{{{0x8b, 0xf1, 0xa0, 0xf5, 0xdc, 0x29, 0xb4, 0xe2, 0x07, 0xc6, 0x7a, 0x00, 0xd0, 0x89, 0x17, 0x51, 0xd4, 0xbb, 0xd4, 0x22, 0xea, 0x7e, 0x7d, 0x7c, 0x24, 0xea, 0xf2, 0xe8, 0x22, 0x12, 0x95, 0x06}} , + {{0xda, 0x7c, 0xa4, 0x0c, 0xf4, 0xba, 0x6e, 0xe1, 0x89, 0xb5, 0x59, 0xca, 0xf1, 0xc0, 0x29, 0x36, 0x09, 0x44, 0xe2, 0x7f, 0xd1, 0x63, 0x15, 0x99, 0xea, 0x25, 0xcf, 0x0c, 0x9d, 0xc0, 0x44, 0x6f}}}, +{{{0x1d, 0x86, 0x4e, 0xcf, 0xf7, 0x37, 0x10, 0x25, 0x8f, 0x12, 0xfb, 0x19, 0xfb, 0xe0, 0xed, 0x10, 0xc8, 0xe2, 0xf5, 0x75, 0xb1, 0x33, 0xc0, 0x96, 0x0d, 0xfb, 0x15, 0x6c, 0x0d, 0x07, 0x5f, 0x05}} , + {{0x69, 0x3e, 0x47, 0x97, 0x2c, 0xaf, 0x52, 0x7c, 0x78, 0x83, 0xad, 0x1b, 0x39, 0x82, 0x2f, 0x02, 0x6f, 0x47, 0xdb, 0x2a, 0xb0, 0xe1, 0x91, 0x99, 0x55, 0xb8, 0x99, 0x3a, 0xa0, 0x44, 0x11, 0x51}}} +}; + +static inline void p1p1_to_p2(ge25519_p2 *r, const ge25519_p1p1 *p) +{ + fe25519_mul(&r->x, &p->x, &p->t); + fe25519_mul(&r->y, &p->y, &p->z); + fe25519_mul(&r->z, &p->z, &p->t); +} + +static inline void p1p1_to_p2_2(ge25519_p3 *r, const ge25519_p1p1 *p) +{ + fe25519_mul(&r->x, &p->x, &p->t); + fe25519_mul(&r->y, &p->y, &p->z); + fe25519_mul(&r->z, &p->z, &p->t); +} + +static inline void p1p1_to_p3(ge25519_p3 *r, const ge25519_p1p1 *p) +{ + p1p1_to_p2_2(r, p); + fe25519_mul(&r->t, &p->x, &p->y); +} + +static void ge25519_mixadd2(ge25519_p3 *r, const ge25519_aff *q) +{ + fe25519 a,b,t1,t2,c,d,e,f,g,h,qt; + fe25519_mul(&qt, &q->x, &q->y); + fe25519_sub(&a, &r->y, &r->x); /* A = (Y1-X1)*(Y2-X2) */ + fe25519_add(&b, &r->y, &r->x); /* B = (Y1+X1)*(Y2+X2) */ + fe25519_sub(&t1, &q->y, &q->x); + fe25519_add(&t2, &q->y, &q->x); + fe25519_mul(&a, &a, &t1); + fe25519_mul(&b, &b, &t2); + fe25519_sub(&e, &b, &a); /* E = B-A */ + fe25519_add(&h, &b, &a); /* H = B+A */ + fe25519_mul(&c, &r->t, &qt); /* C = T1*k*T2 */ + fe25519_mul(&c, &c, &ge25519_ec2d); + fe25519_add(&d, &r->z, &r->z); /* D = Z1*2 */ + fe25519_sub(&f, &d, &c); /* F = D-C */ + fe25519_add(&g, &d, &c); /* G = D+C */ + fe25519_mul(&r->x, &e, &f); + fe25519_mul(&r->y, &h, &g); + fe25519_mul(&r->z, &g, &f); + fe25519_mul(&r->t, &e, &h); +} + +static void add_p1p1(ge25519_p1p1 *r, const ge25519_p3 *p, const ge25519_p3 *q) +{ + fe25519 a, b, c, d, t; + + fe25519_sub(&a, &p->y, &p->x); /* A = (Y1-X1)*(Y2-X2) */ + fe25519_sub(&t, &q->y, &q->x); + fe25519_mul(&a, &a, &t); + fe25519_add(&b, &p->x, &p->y); /* B = (Y1+X1)*(Y2+X2) */ + fe25519_add(&t, &q->x, &q->y); + fe25519_mul(&b, &b, &t); + fe25519_mul(&c, &p->t, &q->t); /* C = T1*k*T2 */ + fe25519_mul(&c, &c, &ge25519_ec2d); + fe25519_mul(&d, &p->z, &q->z); /* D = Z1*2*Z2 */ + fe25519_add(&d, &d, &d); + fe25519_sub(&r->x, &b, &a); /* E = B-A */ + fe25519_sub(&r->t, &d, &c); /* F = D-C */ + fe25519_add(&r->z, &d, &c); /* G = D+C */ + fe25519_add(&r->y, &b, &a); /* H = B+A */ +} + +/* See http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#doubling-dbl-2008-hwcd */ +static void dbl_p1p1(ge25519_p1p1 *r, const ge25519_p2 *p) +{ + fe25519 a,b,c,d; + fe25519_square(&a, &p->x); + fe25519_square(&b, &p->y); + fe25519_square(&c, &p->z); + fe25519_add(&c, &c, &c); + fe25519_neg(&d, &a); + + fe25519_add(&r->x, &p->x, &p->y); + fe25519_square(&r->x, &r->x); + fe25519_sub(&r->x, &r->x, &a); + fe25519_sub(&r->x, &r->x, &b); + fe25519_add(&r->z, &d, &b); + fe25519_sub(&r->t, &r->z, &c); + fe25519_sub(&r->y, &d, &b); +} + +/* Constant-time version of: if(b) r = p */ +static inline void cmov_aff(ge25519_aff *r, const ge25519_aff *p, unsigned char b) +{ + fe25519_cmov(&r->x, &p->x, b); + fe25519_cmov(&r->y, &p->y, b); +} + +static inline unsigned char equal(signed char b,signed char c) +{ + unsigned char ub = b; + unsigned char uc = c; + unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */ + crypto_uint32 y = x; /* 0: yes; 1..255: no */ + y -= 1; /* 4294967295: yes; 0..254: no */ + y >>= 31; /* 1: yes; 0: no */ + return (unsigned char)y; +} + +static inline unsigned char negative(signed char b) +{ + unsigned long long x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ + x >>= 63; /* 1: yes; 0: no */ + return (unsigned char)x; +} + +static inline void choose_t(ge25519_aff *t, unsigned long long pos, signed char b) +{ + /* constant time */ + fe25519 v; + *t = ge25519_base_multiples_affine[5*pos+0]; + cmov_aff(t, &ge25519_base_multiples_affine[5*pos+1],equal(b,1) | equal(b,-1)); + cmov_aff(t, &ge25519_base_multiples_affine[5*pos+2],equal(b,2) | equal(b,-2)); + cmov_aff(t, &ge25519_base_multiples_affine[5*pos+3],equal(b,3) | equal(b,-3)); + cmov_aff(t, &ge25519_base_multiples_affine[5*pos+4],equal(b,-4)); + fe25519_neg(&v, &t->x); + fe25519_cmov(&t->x, &v, negative(b)); +} + +static inline void setneutral(ge25519 *r) +{ + fe25519_setzero(&r->x); + fe25519_setone(&r->y); + fe25519_setone(&r->z); + fe25519_setzero(&r->t); +} + +/* return 0 on success, -1 otherwise */ +static int ge25519_unpackneg_vartime(ge25519_p3 *r, const unsigned char p[32]) +{ + unsigned char par; + fe25519 t, chk, num, den, den2, den4, den6; + fe25519_setone(&r->z); + par = p[31] >> 7; + fe25519_unpack(&r->y, p); + fe25519_square(&num, &r->y); /* x = y^2 */ + fe25519_mul(&den, &num, &ge25519_ecd); /* den = dy^2 */ + fe25519_sub(&num, &num, &r->z); /* x = y^2-1 */ + fe25519_add(&den, &r->z, &den); /* den = dy^2+1 */ + + /* Computation of sqrt(num/den) */ + /* 1.: computation of num^((p-5)/8)*den^((7p-35)/8) = (num*den^7)^((p-5)/8) */ + fe25519_square(&den2, &den); + fe25519_square(&den4, &den2); + fe25519_mul(&den6, &den4, &den2); + fe25519_mul(&t, &den6, &num); + fe25519_mul(&t, &t, &den); + + fe25519_pow2523(&t, &t); + /* 2. computation of r->x = t * num * den^3 */ + fe25519_mul(&t, &t, &num); + fe25519_mul(&t, &t, &den); + fe25519_mul(&t, &t, &den); + fe25519_mul(&r->x, &t, &den); + + /* 3. Check whether sqrt computation gave correct result, multiply by sqrt(-1) if not: */ + fe25519_square(&chk, &r->x); + fe25519_mul(&chk, &chk, &den); + if (!fe25519_iseq_vartime(&chk, &num)) + fe25519_mul(&r->x, &r->x, &ge25519_sqrtm1); + + /* 4. Now we have one of the two square roots, except if input was not a square */ + fe25519_square(&chk, &r->x); + fe25519_mul(&chk, &chk, &den); + if (!fe25519_iseq_vartime(&chk, &num)) + return -1; + + /* 5. Choose the desired square root according to parity: */ + if(fe25519_getparity(&r->x) != (1-par)) + fe25519_neg(&r->x, &r->x); + + fe25519_mul(&r->t, &r->x, &r->y); + return 0; +} + +static inline void ge25519_pack(unsigned char r[32], const ge25519_p3 *p) +{ + fe25519 tx, ty, zi; + fe25519_invert(&zi, &p->z); + fe25519_mul(&tx, &p->x, &zi); + fe25519_mul(&ty, &p->y, &zi); + fe25519_pack(r, &ty); + r[31] ^= fe25519_getparity(&tx) << 7; +} + +#if 0 +static int ge25519_isneutral_vartime(const ge25519_p3 *p) +{ + int ret = 1; + if(!fe25519_iszero(&p->x)) ret = 0; + if(!fe25519_iseq_vartime(&p->y, &p->z)) ret = 0; + return ret; +} +#endif + +/* computes [s1]p1 + [s2]p2 */ +static void ge25519_double_scalarmult_vartime(ge25519_p3 *r, const ge25519_p3 *p1, const sc25519 *s1, const ge25519_p3 *p2, const sc25519 *s2) +{ + ge25519_p1p1 tp1p1; + ge25519_p3 pre[16]; + char *pre5 = (char *)(&(pre[5])); // eliminate type punning warning + unsigned char b[127]; + int i; + + /* precomputation s2 s1 */ + setneutral(pre); /* 00 00 */ + pre[1] = *p1; /* 00 01 */ + dbl_p1p1(&tp1p1,(ge25519_p2 *)p1); p1p1_to_p3( &pre[2], &tp1p1); /* 00 10 */ + add_p1p1(&tp1p1,&pre[1], &pre[2]); p1p1_to_p3( &pre[3], &tp1p1); /* 00 11 */ + pre[4] = *p2; /* 01 00 */ + add_p1p1(&tp1p1,&pre[1], &pre[4]); p1p1_to_p3( &pre[5], &tp1p1); /* 01 01 */ + add_p1p1(&tp1p1,&pre[2], &pre[4]); p1p1_to_p3( &pre[6], &tp1p1); /* 01 10 */ + add_p1p1(&tp1p1,&pre[3], &pre[4]); p1p1_to_p3( &pre[7], &tp1p1); /* 01 11 */ + dbl_p1p1(&tp1p1,(ge25519_p2 *)p2); p1p1_to_p3( &pre[8], &tp1p1); /* 10 00 */ + add_p1p1(&tp1p1,&pre[1], &pre[8]); p1p1_to_p3( &pre[9], &tp1p1); /* 10 01 */ + dbl_p1p1(&tp1p1,(ge25519_p2 *)pre5); p1p1_to_p3(&pre[10], &tp1p1); /* 10 10 */ + add_p1p1(&tp1p1,&pre[3], &pre[8]); p1p1_to_p3(&pre[11], &tp1p1); /* 10 11 */ + add_p1p1(&tp1p1,&pre[4], &pre[8]); p1p1_to_p3(&pre[12], &tp1p1); /* 11 00 */ + add_p1p1(&tp1p1,&pre[1],&pre[12]); p1p1_to_p3(&pre[13], &tp1p1); /* 11 01 */ + add_p1p1(&tp1p1,&pre[2],&pre[12]); p1p1_to_p3(&pre[14], &tp1p1); /* 11 10 */ + add_p1p1(&tp1p1,&pre[3],&pre[12]); p1p1_to_p3(&pre[15], &tp1p1); /* 11 11 */ + + sc25519_2interleave2(b,s1,s2); + + /* scalar multiplication */ + *r = pre[b[126]]; + for(i=125;i>=0;i--) + { + dbl_p1p1(&tp1p1, (ge25519_p2 *)r); + p1p1_to_p2((ge25519_p2 *) r, &tp1p1); + dbl_p1p1(&tp1p1, (ge25519_p2 *)r); + if(b[i]!=0) + { + p1p1_to_p3(r, &tp1p1); + add_p1p1(&tp1p1, r, &pre[b[i]]); + } + if(i != 0) p1p1_to_p2((ge25519_p2 *)r, &tp1p1); + else p1p1_to_p3(r, &tp1p1); + } +} + +static inline void ge25519_scalarmult_base(ge25519_p3 *r, const sc25519 *s) +{ + signed char b[85]; + int i; + ge25519_aff t; + sc25519_window3(b,s); + + choose_t((ge25519_aff *)r, 0, b[0]); + fe25519_setone(&r->z); + fe25519_mul(&r->t, &r->x, &r->y); + for(i=1;i<85;i++) + { + choose_t(&t, (unsigned long long) i, b[i]); + ge25519_mixadd2(r, &t); + } +} + +static inline void get_hram(unsigned char *hram, const unsigned char *sm, const unsigned char *pk, unsigned char *playground, unsigned long long smlen) +{ + unsigned long long i; + + for (i = 0;i < 32;++i) playground[i] = sm[i]; + for (i = 32;i < 64;++i) playground[i] = pk[i-32]; + for (i = 64;i < smlen;++i) playground[i] = sm[i]; + + //crypto_hash_sha512(hram,playground,smlen); + SHA512::hash(hram,playground,(unsigned int)smlen); +} + +// This is the original sign and verify code -- the versions in sign() and +// verify() below the fold are slightly modified in terms of how they behave +// in relation to the message, but the algorithms are the same. + +#if 0 +int crypto_sign_keypair( + unsigned char *pk, + unsigned char *sk + ) +{ + sc25519 scsk; + ge25519 gepk; + unsigned char extsk[64]; + int i; + + randombytes(sk, 32); + crypto_hash_sha512(extsk, sk, 32); + extsk[0] &= 248; + extsk[31] &= 127; + extsk[31] |= 64; + + sc25519_from32bytes(&scsk,extsk); + + ge25519_scalarmult_base(&gepk, &scsk); + ge25519_pack(pk, &gepk); + for(i=0;i<32;i++) + sk[32 + i] = pk[i]; + return 0; +} + +static int crypto_sign( + unsigned char *sm,unsigned long long *smlen, + const unsigned char *m,unsigned long long mlen, + const unsigned char *sk + ) +{ + sc25519 sck, scs, scsk; + ge25519 ger; + unsigned char r[32]; + unsigned char s[32]; + unsigned char extsk[64]; + unsigned long long i; + unsigned char hmg[crypto_hash_sha512_BYTES]; + unsigned char hram[crypto_hash_sha512_BYTES]; + + crypto_hash_sha512(extsk, sk, 32); + extsk[0] &= 248; + extsk[31] &= 127; + extsk[31] |= 64; + + *smlen = mlen+64; + for(i=0;i. + */ + +#ifndef ZT_C25519_HPP +#define ZT_C25519_HPP + +#include "Array.hpp" +#include "Utils.hpp" + +namespace ZeroTier { + +#define ZT_C25519_PUBLIC_KEY_LEN 64 +#define ZT_C25519_PRIVATE_KEY_LEN 64 +#define ZT_C25519_SIGNATURE_LEN 96 + +/** + * A combined Curve25519 ECDH and Ed25519 signature engine + */ +class C25519 +{ +public: + /** + * Public key (both crypto and signing) + */ + typedef Array Public; // crypto key, signing key (both 32 bytes) + + /** + * Private key (both crypto and signing) + */ + typedef Array Private; // crypto key, signing key (both 32 bytes) + + /** + * Message signature + */ + typedef Array Signature; + + /** + * Public/private key pair + */ + typedef struct { + Public pub; + Private priv; + } Pair; + + /** + * Generate a C25519 elliptic curve key pair + */ + static inline Pair generate() + throw() + { + Pair kp; + Utils::getSecureRandom(kp.priv.data,(unsigned int)kp.priv.size()); + _calcPubDH(kp); + _calcPubED(kp); + return kp; + } + + /** + * Generate a key pair satisfying a condition + * + * This begins with a random keypair from a random secret key and then + * iteratively increments the random secret until cond(kp) returns true. + * This is used to compute key pairs in which the public key, its hash + * or some other aspect of it satisfies some condition, such as for a + * hashcash criteria. + * + * @param cond Condition function or function object + * @return Key pair where cond(kp) returns true + * @tparam F Type of 'cond' + */ + template + static inline Pair generateSatisfying(F cond) + throw() + { + Pair kp; + void *const priv = (void *)kp.priv.data; + Utils::getSecureRandom(priv,(unsigned int)kp.priv.size()); + _calcPubED(kp); // do Ed25519 key -- bytes 32-63 of pub and priv + do { + ++(((uint64_t *)priv)[1]); + --(((uint64_t *)priv)[2]); + _calcPubDH(kp); // keep regenerating bytes 0-31 until satisfied + } while (!cond(kp)); + return kp; + } + + /** + * Perform C25519 ECC key agreement + * + * Actual key bytes are generated from one or more SHA-512 digests of + * the raw result of key agreement. + * + * @param mine My private key + * @param their Their public key + * @param keybuf Buffer to fill + * @param keylen Number of key bytes to generate + */ + static void agree(const Private &mine,const Public &their,void *keybuf,unsigned int keylen) + throw(); + static inline void agree(const Pair &mine,const Public &their,void *keybuf,unsigned int keylen) + throw() + { + agree(mine.priv,their,keybuf,keylen); + } + + /** + * Sign a message with a sender's key pair + * + * This takes the SHA-521 of msg[] and then signs the first 32 bytes of this + * digest, returning it and the 64-byte ed25519 signature in signature[]. + * This results in a signature that verifies both the signer's authenticity + * and the integrity of the message. + * + * This is based on the original ed25519 code from NaCl and the SUPERCOP + * cipher benchmark suite, but with the modification that it always + * produces a signature of fixed 96-byte length based on the hash of an + * arbitrary-length message. + * + * @param myPrivate My private key + * @param myPublic My public key + * @param msg Message to sign + * @param len Length of message in bytes + * @param signature Buffer to fill with signature -- MUST be 96 bytes in length + */ + static void sign(const Private &myPrivate,const Public &myPublic,const void *msg,unsigned int len,void *signature) + throw(); + static inline void sign(const Pair &mine,const void *msg,unsigned int len,void *signature) + throw() + { + sign(mine.priv,mine.pub,msg,len,signature); + } + + /** + * Sign a message with a sender's key pair + * + * @param myPrivate My private key + * @param myPublic My public key + * @param msg Message to sign + * @param len Length of message in bytes + * @return Signature + */ + static inline Signature sign(const Private &myPrivate,const Public &myPublic,const void *msg,unsigned int len) + throw() + { + Signature sig; + sign(myPrivate,myPublic,msg,len,sig.data); + return sig; + } + static inline Signature sign(const Pair &mine,const void *msg,unsigned int len) + throw() + { + Signature sig; + sign(mine.priv,mine.pub,msg,len,sig.data); + return sig; + } + + /** + * Verify a message's signature + * + * @param their Public key to verify against + * @param msg Message to verify signature integrity against + * @param len Length of message in bytes + * @param signature 96-byte signature + * @return True if signature is valid and the message is authentic and unmodified + */ + static bool verify(const Public &their,const void *msg,unsigned int len,const void *signature) + throw(); + + /** + * Verify a message's signature + * + * @param their Public key to verify against + * @param msg Message to verify signature integrity against + * @param len Length of message in bytes + * @param signature 96-byte signature + * @return True if signature is valid and the message is authentic and unmodified + */ + static inline bool verify(const Public &their,const void *msg,unsigned int len,const Signature &signature) + throw() + { + return verify(their,msg,len,signature.data); + } + +private: + // derive first 32 bytes of kp.pub from first 32 bytes of kp.priv + // this is the ECDH key + static void _calcPubDH(Pair &kp) + throw(); + + // derive 2nd 32 bytes of kp.pub from 2nd 32 bytes of kp.priv + // this is the Ed25519 sign/verify key + static void _calcPubED(Pair &kp) + throw(); +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Capability.cpp b/node/Capability.cpp new file mode 100644 index 0000000..c178e56 --- /dev/null +++ b/node/Capability.cpp @@ -0,0 +1,65 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include "Capability.hpp" +#include "RuntimeEnvironment.hpp" +#include "Identity.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Network.hpp" + +namespace ZeroTier { + +int Capability::verify(const RuntimeEnvironment *RR,void *tPtr) const +{ + try { + // There must be at least one entry, and sanity check for bad chain max length + if ((_maxCustodyChainLength < 1)||(_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) + return -1; + + // Validate all entries in chain of custody + Buffer<(sizeof(Capability) * 2)> tmp; + this->serialize(tmp,true); + for(unsigned int c=0;c<_maxCustodyChainLength;++c) { + if (c == 0) { + if ((!_custody[c].to)||(!_custody[c].from)||(_custody[c].from != Network::controllerFor(_nwid))) + return -1; // the first entry must be present and from the network's controller + } else { + if (!_custody[c].to) + return 0; // all previous entries were valid, so we are valid + else if ((!_custody[c].from)||(_custody[c].from != _custody[c-1].to)) + return -1; // otherwise if we have another entry it must be from the previous holder in the chain + } + + const Identity id(RR->topology->getIdentity(tPtr,_custody[c].from)); + if (id) { + if (!id.verify(tmp.data(),tmp.size(),_custody[c].signature)) + return -1; + } else { + RR->sw->requestWhois(tPtr,_custody[c].from); + return 1; + } + } + + // We reached max custody chain length and everything was valid + return 0; + } catch ( ... ) {} + return -1; +} + +} // namespace ZeroTier diff --git a/node/Capability.hpp b/node/Capability.hpp new file mode 100644 index 0000000..454723a --- /dev/null +++ b/node/Capability.hpp @@ -0,0 +1,472 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_CAPABILITY_HPP +#define ZT_CAPABILITY_HPP + +#include +#include +#include + +#include "Constants.hpp" +#include "Credential.hpp" +#include "Address.hpp" +#include "C25519.hpp" +#include "Utils.hpp" +#include "Buffer.hpp" +#include "Identity.hpp" +#include "../include/ZeroTierOne.h" + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * A set of grouped and signed network flow rules + * + * On the sending side the sender does the following for each packet: + * + * (1) Evaluates its capabilities in ascending order of ID to determine + * which capability allows it to transmit this packet. + * (2) If it has not done so lately, it then sends this capability to the + * receving peer ("presents" it). + * (3) The sender then sends the packet. + * + * On the receiving side the receiver evaluates the capabilities presented + * by the sender. If any valid un-expired capability allows this packet it + * is accepted. + * + * Note that this is after evaluation of network scope rules and only if + * network scope rules do not deliver an explicit match. + * + * Capabilities support a chain of custody. This is currently unused but + * in the future would allow the publication of capabilities that can be + * handed off between nodes. Limited transferrability of capabilities is + * a feature of true capability based security. + */ +class Capability : public Credential +{ +public: + static inline Credential::Type credentialType() { return Credential::CREDENTIAL_TYPE_CAPABILITY; } + + Capability() + { + memset(this,0,sizeof(Capability)); + } + + /** + * @param id Capability ID + * @param nwid Network ID + * @param ts Timestamp (at controller) + * @param mccl Maximum custody chain length (1 to create non-transferrable capability) + * @param rules Network flow rules for this capability + * @param ruleCount Number of flow rules + */ + Capability(uint32_t id,uint64_t nwid,uint64_t ts,unsigned int mccl,const ZT_VirtualNetworkRule *rules,unsigned int ruleCount) + { + memset(this,0,sizeof(Capability)); + _nwid = nwid; + _ts = ts; + _id = id; + _maxCustodyChainLength = (mccl > 0) ? ((mccl < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) ? mccl : (unsigned int)ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH) : 1; + _ruleCount = (ruleCount < ZT_MAX_CAPABILITY_RULES) ? ruleCount : ZT_MAX_CAPABILITY_RULES; + if (_ruleCount) + memcpy(_rules,rules,sizeof(ZT_VirtualNetworkRule) * _ruleCount); + } + + /** + * @return Rules -- see ruleCount() for size of array + */ + inline const ZT_VirtualNetworkRule *rules() const { return _rules; } + + /** + * @return Number of rules in rules() + */ + inline unsigned int ruleCount() const { return _ruleCount; } + + /** + * @return ID and evaluation order of this capability in network + */ + inline uint32_t id() const { return _id; } + + /** + * @return Network ID for which this capability was issued + */ + inline uint64_t networkId() const { return _nwid; } + + /** + * @return Timestamp + */ + inline uint64_t timestamp() const { return _ts; } + + /** + * @return Last 'to' address in chain of custody + */ + inline Address issuedTo() const + { + Address i2; + for(unsigned int i=0;i tmp; + this->serialize(tmp,true); + _custody[i].to = to; + _custody[i].from = from.address(); + _custody[i].signature = from.sign(tmp.data(),tmp.size()); + return true; + } + } + } catch ( ... ) {} + return false; + } + + /** + * Verify this capability's chain of custody and signatures + * + * @param RR Runtime environment to provide for peer lookup, etc. + * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or chain + */ + int verify(const RuntimeEnvironment *RR,void *tPtr) const; + + template + static inline void serializeRules(Buffer &b,const ZT_VirtualNetworkRule *rules,unsigned int ruleCount) + { + for(unsigned int i=0;i + static inline void deserializeRules(const Buffer &b,unsigned int &p,ZT_VirtualNetworkRule *rules,unsigned int &ruleCount,const unsigned int maxRuleCount) + { + while ((ruleCount < maxRuleCount)&&(p < b.size())) { + rules[ruleCount].t = (uint8_t)b[p++]; + const unsigned int fieldLen = (unsigned int)b[p++]; + switch((ZT_VirtualNetworkRuleType)(rules[ruleCount].t & 0x3f)) { + default: + break; + case ZT_NETWORK_RULE_ACTION_TEE: + case ZT_NETWORK_RULE_ACTION_WATCH: + case ZT_NETWORK_RULE_ACTION_REDIRECT: + rules[ruleCount].v.fwd.address = b.template at(p); + rules[ruleCount].v.fwd.flags = b.template at(p + 8); + rules[ruleCount].v.fwd.length = b.template at(p + 12); + break; + case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: + case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: + rules[ruleCount].v.zt = Address(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH).toInt(); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_ID: + rules[ruleCount].v.vlanId = b.template at(p); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_PCP: + rules[ruleCount].v.vlanPcp = (uint8_t)b[p]; + break; + case ZT_NETWORK_RULE_MATCH_VLAN_DEI: + rules[ruleCount].v.vlanDei = (uint8_t)b[p]; + break; + case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: + case ZT_NETWORK_RULE_MATCH_MAC_DEST: + memcpy(rules[ruleCount].v.mac,b.field(p,6),6); + break; + case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: + case ZT_NETWORK_RULE_MATCH_IPV4_DEST: + memcpy(&(rules[ruleCount].v.ipv4.ip),b.field(p,4),4); + rules[ruleCount].v.ipv4.mask = (uint8_t)b[p + 4]; + break; + case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE: + case ZT_NETWORK_RULE_MATCH_IPV6_DEST: + memcpy(rules[ruleCount].v.ipv6.ip,b.field(p,16),16); + rules[ruleCount].v.ipv6.mask = (uint8_t)b[p + 16]; + break; + case ZT_NETWORK_RULE_MATCH_IP_TOS: + rules[ruleCount].v.ipTos.mask = (uint8_t)b[p]; + rules[ruleCount].v.ipTos.value[0] = (uint8_t)b[p+1]; + rules[ruleCount].v.ipTos.value[1] = (uint8_t)b[p+2]; + break; + case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: + rules[ruleCount].v.ipProtocol = (uint8_t)b[p]; + break; + case ZT_NETWORK_RULE_MATCH_ETHERTYPE: + rules[ruleCount].v.etherType = b.template at(p); + break; + case ZT_NETWORK_RULE_MATCH_ICMP: + rules[ruleCount].v.icmp.type = (uint8_t)b[p]; + rules[ruleCount].v.icmp.code = (uint8_t)b[p+1]; + rules[ruleCount].v.icmp.flags = (uint8_t)b[p+2]; + break; + case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: + case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: + rules[ruleCount].v.port[0] = b.template at(p); + rules[ruleCount].v.port[1] = b.template at(p + 2); + break; + case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: + rules[ruleCount].v.characteristics = b.template at(p); + break; + case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE: + rules[ruleCount].v.frameSize[0] = b.template at(p); + rules[ruleCount].v.frameSize[1] = b.template at(p + 2); + break; + case ZT_NETWORK_RULE_MATCH_RANDOM: + rules[ruleCount].v.randomProbability = b.template at(p); + break; + case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE: + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND: + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR: + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR: + case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL: + case ZT_NETWORK_RULE_MATCH_TAG_SENDER: + case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER: + rules[ruleCount].v.tag.id = b.template at(p); + rules[ruleCount].v.tag.value = b.template at(p + 4); + break; + } + p += fieldLen; + ++ruleCount; + } + } + + template + inline void serialize(Buffer &b,const bool forSign = false) const + { + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + + // These are the same between Tag and Capability + b.append(_nwid); + b.append(_ts); + b.append(_id); + + b.append((uint16_t)_ruleCount); + serializeRules(b,_rules,_ruleCount); + b.append((uint8_t)_maxCustodyChainLength); + + if (!forSign) { + for(unsigned int i=0;;++i) { + if ((i < _maxCustodyChainLength)&&(i < ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)&&(_custody[i].to)) { + _custody[i].to.appendTo(b); + _custody[i].from.appendTo(b); + b.append((uint8_t)1); // 1 == Ed25519 signature + b.append((uint16_t)ZT_C25519_SIGNATURE_LEN); // length of signature + b.append(_custody[i].signature.data,ZT_C25519_SIGNATURE_LEN); + } else { + b.append((unsigned char)0,ZT_ADDRESS_LENGTH); // zero 'to' terminates chain + break; + } + } + } + + // This is the size of any additional fields, currently 0. + b.append((uint16_t)0); + + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } + + template + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + memset(this,0,sizeof(Capability)); + + unsigned int p = startAt; + + _nwid = b.template at(p); p += 8; + _ts = b.template at(p); p += 8; + _id = b.template at(p); p += 4; + + const unsigned int rc = b.template at(p); p += 2; + if (rc > ZT_MAX_CAPABILITY_RULES) + throw std::runtime_error("rule overflow"); + deserializeRules(b,p,_rules,_ruleCount,rc); + + _maxCustodyChainLength = (unsigned int)b[p++]; + if ((_maxCustodyChainLength < 1)||(_maxCustodyChainLength > ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) + throw std::runtime_error("invalid max custody chain length"); + + for(unsigned int i=0;;++i) { + const Address to(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + if (!to) + break; + if ((i >= _maxCustodyChainLength)||(i >= ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH)) + throw std::runtime_error("unterminated custody chain"); + _custody[i].to = to; + _custody[i].from.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + if (b[p++] == 1) { + if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) + throw std::runtime_error("invalid signature"); + p += 2; + memcpy(_custody[i].signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + } else { + p += 2 + b.template at(p); + } + } + + p += 2 + b.template at(p); + if (p > b.size()) + throw std::runtime_error("extended field overflow"); + + return (p - startAt); + } + + // Provides natural sort order by ID + inline bool operator<(const Capability &c) const { return (_id < c._id); } + + inline bool operator==(const Capability &c) const { return (memcmp(this,&c,sizeof(Capability)) == 0); } + inline bool operator!=(const Capability &c) const { return (memcmp(this,&c,sizeof(Capability)) != 0); } + +private: + uint64_t _nwid; + uint64_t _ts; + uint32_t _id; + + unsigned int _maxCustodyChainLength; + + unsigned int _ruleCount; + ZT_VirtualNetworkRule _rules[ZT_MAX_CAPABILITY_RULES]; + + struct { + Address to; + Address from; + C25519::Signature signature; + } _custody[ZT_MAX_CAPABILITY_CUSTODY_CHAIN_LENGTH]; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/CertificateOfMembership.cpp b/node/CertificateOfMembership.cpp new file mode 100644 index 0000000..9bf7021 --- /dev/null +++ b/node/CertificateOfMembership.cpp @@ -0,0 +1,231 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include "CertificateOfMembership.hpp" +#include "RuntimeEnvironment.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Network.hpp" + +namespace ZeroTier { + +void CertificateOfMembership::setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta) +{ + _signedBy.zero(); + + for(unsigned int i=0;i<_qualifierCount;++i) { + if (_qualifiers[i].id == id) { + _qualifiers[i].value = value; + _qualifiers[i].maxDelta = maxDelta; + return; + } + } + + if (_qualifierCount < ZT_NETWORK_COM_MAX_QUALIFIERS) { + _qualifiers[_qualifierCount].id = id; + _qualifiers[_qualifierCount].value = value; + _qualifiers[_qualifierCount].maxDelta = maxDelta; + ++_qualifierCount; + std::sort(&(_qualifiers[0]),&(_qualifiers[_qualifierCount])); + } +} + +#ifdef ZT_SUPPORT_OLD_STYLE_NETCONF + +std::string CertificateOfMembership::toString() const +{ + std::string s; + + s.append("1:"); // COM_UINT64_ED25519 + + uint64_t *const buf = new uint64_t[_qualifierCount * 3]; + try { + unsigned int ptr = 0; + for(unsigned int i=0;i<_qualifierCount;++i) { + buf[ptr++] = Utils::hton(_qualifiers[i].id); + buf[ptr++] = Utils::hton(_qualifiers[i].value); + buf[ptr++] = Utils::hton(_qualifiers[i].maxDelta); + } + s.append(Utils::hex(buf,ptr * sizeof(uint64_t))); + delete [] buf; + } catch ( ... ) { + delete [] buf; + throw; + } + + s.push_back(':'); + + s.append(_signedBy.toString()); + + if (_signedBy) { + s.push_back(':'); + s.append(Utils::hex(_signature.data,(unsigned int)_signature.size())); + } + + return s; +} + +void CertificateOfMembership::fromString(const char *s) +{ + _qualifierCount = 0; + _signedBy.zero(); + memset(_signature.data,0,_signature.size()); + + if (!*s) + return; + + unsigned int colonAt = 0; + while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt; + + if (!((colonAt == 1)&&(s[0] == '1'))) // COM_UINT64_ED25519? + return; + + s += colonAt + 1; + colonAt = 0; + while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt; + + if (colonAt) { + const unsigned int buflen = colonAt / 2; + char *const buf = new char[buflen]; + unsigned int bufactual = Utils::unhex(s,colonAt,buf,buflen); + char *bufptr = buf; + try { + while (bufactual >= 24) { + if (_qualifierCount < ZT_NETWORK_COM_MAX_QUALIFIERS) { + _qualifiers[_qualifierCount].id = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8; + _qualifiers[_qualifierCount].value = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8; + _qualifiers[_qualifierCount].maxDelta = Utils::ntoh(*((uint64_t *)bufptr)); bufptr += 8; + ++_qualifierCount; + } else { + bufptr += 24; + } + bufactual -= 24; + } + } catch ( ... ) {} + delete [] buf; + } + + if (s[colonAt]) { + s += colonAt + 1; + colonAt = 0; + while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt; + + if (colonAt) { + char addrbuf[ZT_ADDRESS_LENGTH]; + if (Utils::unhex(s,colonAt,addrbuf,sizeof(addrbuf)) == ZT_ADDRESS_LENGTH) + _signedBy.setTo(addrbuf,ZT_ADDRESS_LENGTH); + + if ((_signedBy)&&(s[colonAt])) { + s += colonAt + 1; + colonAt = 0; + while ((s[colonAt])&&(s[colonAt] != ':')) ++colonAt; + if (colonAt) { + if (Utils::unhex(s,colonAt,_signature.data,(unsigned int)_signature.size()) != _signature.size()) + _signedBy.zero(); + } else { + _signedBy.zero(); + } + } else { + _signedBy.zero(); + } + } + } + + std::sort(&(_qualifiers[0]),&(_qualifiers[_qualifierCount])); +} + +#endif // ZT_SUPPORT_OLD_STYLE_NETCONF + +bool CertificateOfMembership::agreesWith(const CertificateOfMembership &other) const +{ + unsigned int myidx = 0; + unsigned int otheridx = 0; + + if ((_qualifierCount == 0)||(other._qualifierCount == 0)) + return false; + + while (myidx < _qualifierCount) { + // Fail if we're at the end of other, since this means the field is + // missing. + if (otheridx >= other._qualifierCount) + return false; + + // Seek to corresponding tuple in other, ignoring tuples that + // we may not have. If we run off the end of other, the tuple is + // missing. This works because tuples are sorted by ID. + while (other._qualifiers[otheridx].id != _qualifiers[myidx].id) { + ++otheridx; + if (otheridx >= other._qualifierCount) + return false; + } + + // Compare to determine if the absolute value of the difference + // between these two parameters is within our maxDelta. + const uint64_t a = _qualifiers[myidx].value; + const uint64_t b = other._qualifiers[myidx].value; + if (((a >= b) ? (a - b) : (b - a)) > _qualifiers[myidx].maxDelta) + return false; + + ++myidx; + } + + return true; +} + +bool CertificateOfMembership::sign(const Identity &with) +{ + uint64_t buf[ZT_NETWORK_COM_MAX_QUALIFIERS * 3]; + unsigned int ptr = 0; + for(unsigned int i=0;i<_qualifierCount;++i) { + buf[ptr++] = Utils::hton(_qualifiers[i].id); + buf[ptr++] = Utils::hton(_qualifiers[i].value); + buf[ptr++] = Utils::hton(_qualifiers[i].maxDelta); + } + + try { + _signature = with.sign(buf,ptr * sizeof(uint64_t)); + _signedBy = with.address(); + return true; + } catch ( ... ) { + _signedBy.zero(); + return false; + } +} + +int CertificateOfMembership::verify(const RuntimeEnvironment *RR,void *tPtr) const +{ + if ((!_signedBy)||(_signedBy != Network::controllerFor(networkId()))||(_qualifierCount > ZT_NETWORK_COM_MAX_QUALIFIERS)) + return -1; + + const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); + if (!id) { + RR->sw->requestWhois(tPtr,_signedBy); + return 1; + } + + uint64_t buf[ZT_NETWORK_COM_MAX_QUALIFIERS * 3]; + unsigned int ptr = 0; + for(unsigned int i=0;i<_qualifierCount;++i) { + buf[ptr++] = Utils::hton(_qualifiers[i].id); + buf[ptr++] = Utils::hton(_qualifiers[i].value); + buf[ptr++] = Utils::hton(_qualifiers[i].maxDelta); + } + return (id.verify(buf,ptr * sizeof(uint64_t),_signature) ? 0 : -1); +} + +} // namespace ZeroTier diff --git a/node/CertificateOfMembership.hpp b/node/CertificateOfMembership.hpp new file mode 100644 index 0000000..dfccb13 --- /dev/null +++ b/node/CertificateOfMembership.hpp @@ -0,0 +1,365 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_CERTIFICATEOFMEMBERSHIP_HPP +#define ZT_CERTIFICATEOFMEMBERSHIP_HPP + +#include +#include + +#include +#include +#include + +#include "Constants.hpp" +#include "Credential.hpp" +#include "Buffer.hpp" +#include "Address.hpp" +#include "C25519.hpp" +#include "Identity.hpp" +#include "Utils.hpp" + +/** + * Maximum number of qualifiers allowed in a COM (absolute max: 65535) + */ +#define ZT_NETWORK_COM_MAX_QUALIFIERS 8 + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Certificate of network membership + * + * The COM contains a sorted set of three-element tuples called qualifiers. + * These contain an id, a value, and a maximum delta. + * + * The ID is arbitrary and should be assigned using a scheme that makes + * every ID globally unique. IDs beneath 65536 are reserved for global + * assignment by ZeroTier Networks. + * + * The value's meaning is ID-specific and isn't important here. What's + * important is the value and the third member of the tuple: the maximum + * delta. The maximum delta is the maximum difference permitted between + * values for a given ID between certificates for the two certificates to + * themselves agree. + * + * Network membership is checked by checking whether a peer's certificate + * agrees with your own. The timestamp provides the fundamental criterion-- + * each member of a private network must constantly obtain new certificates + * often enough to stay within the max delta for this qualifier. But other + * criteria could be added in the future for very special behaviors, things + * like latitude and longitude for instance. + * + * This is a memcpy()'able structure and is safe (in a crash sense) to modify + * without locks. + */ +class CertificateOfMembership : public Credential +{ +public: + static inline Credential::Type credentialType() { return Credential::CREDENTIAL_TYPE_COM; } + + /** + * Reserved qualifier IDs + * + * IDs below 1024 are reserved for use as standard IDs. Others are available + * for user-defined use. + * + * Addition of new required fields requires that code in hasRequiredFields + * be updated as well. + */ + enum ReservedId + { + /** + * Timestamp of certificate + */ + COM_RESERVED_ID_TIMESTAMP = 0, + + /** + * Network ID for which certificate was issued + */ + COM_RESERVED_ID_NETWORK_ID = 1, + + /** + * ZeroTier address to whom certificate was issued + */ + COM_RESERVED_ID_ISSUED_TO = 2 + }; + + /** + * Create an empty certificate of membership + */ + CertificateOfMembership() + { + memset(this,0,sizeof(CertificateOfMembership)); + } + + CertificateOfMembership(const CertificateOfMembership &c) + { + memcpy(this,&c,sizeof(CertificateOfMembership)); + } + + /** + * Create from required fields common to all networks + * + * @param timestamp Timestamp of certificate + * @param timestampMaxDelta Maximum variation between timestamps on this net + * @param nwid Network ID + * @param issuedTo Certificate recipient + */ + CertificateOfMembership(uint64_t timestamp,uint64_t timestampMaxDelta,uint64_t nwid,const Address &issuedTo) + { + _qualifiers[0].id = COM_RESERVED_ID_TIMESTAMP; + _qualifiers[0].value = timestamp; + _qualifiers[0].maxDelta = timestampMaxDelta; + _qualifiers[1].id = COM_RESERVED_ID_NETWORK_ID; + _qualifiers[1].value = nwid; + _qualifiers[1].maxDelta = 0; + _qualifiers[2].id = COM_RESERVED_ID_ISSUED_TO; + _qualifiers[2].value = issuedTo.toInt(); + _qualifiers[2].maxDelta = 0xffffffffffffffffULL; + _qualifierCount = 3; + memset(_signature.data,0,_signature.size()); + } + + inline CertificateOfMembership &operator=(const CertificateOfMembership &c) + { + memcpy(this,&c,sizeof(CertificateOfMembership)); + return *this; + } + + /** + * Create from binary-serialized COM in buffer + * + * @param b Buffer to deserialize from + * @param startAt Position to start in buffer + */ + template + CertificateOfMembership(const Buffer &b,unsigned int startAt = 0) + { + deserialize(b,startAt); + } + + /** + * @return True if there's something here + */ + inline operator bool() const { return (_qualifierCount != 0); } + + /** + * @return Credential ID, always 0 for COMs + */ + inline uint32_t id() const { return 0; } + + /** + * @return Timestamp for this cert and maximum delta for timestamp + */ + inline uint64_t timestamp() const + { + for(unsigned int i=0;i<_qualifierCount;++i) { + if (_qualifiers[i].id == COM_RESERVED_ID_TIMESTAMP) + return _qualifiers[i].value; + } + return 0; + } + + /** + * @return Address to which this cert was issued + */ + inline Address issuedTo() const + { + for(unsigned int i=0;i<_qualifierCount;++i) { + if (_qualifiers[i].id == COM_RESERVED_ID_ISSUED_TO) + return Address(_qualifiers[i].value); + } + return Address(); + } + + /** + * @return Network ID for which this cert was issued + */ + inline uint64_t networkId() const + { + for(unsigned int i=0;i<_qualifierCount;++i) { + if (_qualifiers[i].id == COM_RESERVED_ID_NETWORK_ID) + return _qualifiers[i].value; + } + return 0ULL; + } + + /** + * Add or update a qualifier in this certificate + * + * Any signature is invalidated and signedBy is set to null. + * + * @param id Qualifier ID + * @param value Qualifier value + * @param maxDelta Qualifier maximum allowed difference (absolute value of difference) + */ + void setQualifier(uint64_t id,uint64_t value,uint64_t maxDelta); + inline void setQualifier(ReservedId id,uint64_t value,uint64_t maxDelta) { setQualifier((uint64_t)id,value,maxDelta); } + +#ifdef ZT_SUPPORT_OLD_STYLE_NETCONF + /** + * @return String-serialized representation of this certificate + */ + std::string toString() const; + + /** + * Set this certificate equal to the hex-serialized string + * + * Invalid strings will result in invalid or undefined certificate + * contents. These will subsequently fail validation and comparison. + * Empty strings will result in an empty certificate. + * + * @param s String to deserialize + */ + void fromString(const char *s); +#endif // ZT_SUPPORT_OLD_STYLE_NETCONF + + /** + * Compare two certificates for parameter agreement + * + * This compares this certificate with the other and returns true if all + * paramters in this cert are present in the other and if they agree to + * within this cert's max delta value for each given parameter. + * + * Tuples present in other but not in this cert are ignored, but any + * tuples present in this cert but not in other result in 'false'. + * + * @param other Cert to compare with + * @return True if certs agree and 'other' may be communicated with + */ + bool agreesWith(const CertificateOfMembership &other) const; + + /** + * Sign this certificate + * + * @param with Identity to sign with, must include private key + * @return True if signature was successful + */ + bool sign(const Identity &with); + + /** + * Verify this COM and its signature + * + * @param RR Runtime environment for looking up peers + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or credential + */ + int verify(const RuntimeEnvironment *RR,void *tPtr) const; + + /** + * @return True if signed + */ + inline bool isSigned() const { return (_signedBy); } + + /** + * @return Address that signed this certificate or null address if none + */ + inline const Address &signedBy() const { return _signedBy; } + + template + inline void serialize(Buffer &b) const + { + b.append((uint8_t)1); + b.append((uint16_t)_qualifierCount); + for(unsigned int i=0;i<_qualifierCount;++i) { + b.append(_qualifiers[i].id); + b.append(_qualifiers[i].value); + b.append(_qualifiers[i].maxDelta); + } + _signedBy.appendTo(b); + if (_signedBy) + b.append(_signature.data,(unsigned int)_signature.size()); + } + + template + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + unsigned int p = startAt; + + _qualifierCount = 0; + _signedBy.zero(); + + if (b[p++] != 1) + throw std::invalid_argument("invalid object"); + + unsigned int numq = b.template at(p); p += sizeof(uint16_t); + uint64_t lastId = 0; + for(unsigned int i=0;i(p); + if (qid < lastId) + throw std::invalid_argument("qualifiers not sorted"); + else lastId = qid; + if (_qualifierCount < ZT_NETWORK_COM_MAX_QUALIFIERS) { + _qualifiers[_qualifierCount].id = qid; + _qualifiers[_qualifierCount].value = b.template at(p + 8); + _qualifiers[_qualifierCount].maxDelta = b.template at(p + 16); + p += 24; + ++_qualifierCount; + } else { + throw std::invalid_argument("too many qualifiers"); + } + } + + _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; + + if (_signedBy) { + memcpy(_signature.data,b.field(p,(unsigned int)_signature.size()),_signature.size()); + p += (unsigned int)_signature.size(); + } + + return (p - startAt); + } + + inline bool operator==(const CertificateOfMembership &c) const + { + if (_signedBy != c._signedBy) + return false; + if (_qualifierCount != c._qualifierCount) + return false; + for(unsigned int i=0;i<_qualifierCount;++i) { + const _Qualifier &a = _qualifiers[i]; + const _Qualifier &b = c._qualifiers[i]; + if ((a.id != b.id)||(a.value != b.value)||(a.maxDelta != b.maxDelta)) + return false; + } + return (_signature == c._signature); + } + inline bool operator!=(const CertificateOfMembership &c) const { return (!(*this == c)); } + +private: + struct _Qualifier + { + _Qualifier() : id(0),value(0),maxDelta(0) {} + uint64_t id; + uint64_t value; + uint64_t maxDelta; + inline bool operator<(const _Qualifier &q) const throw() { return (id < q.id); } // sort order + }; + + Address _signedBy; + _Qualifier _qualifiers[ZT_NETWORK_COM_MAX_QUALIFIERS]; + unsigned int _qualifierCount; + C25519::Signature _signature; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/CertificateOfOwnership.cpp b/node/CertificateOfOwnership.cpp new file mode 100644 index 0000000..2bd181e --- /dev/null +++ b/node/CertificateOfOwnership.cpp @@ -0,0 +1,63 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include "CertificateOfOwnership.hpp" +#include "RuntimeEnvironment.hpp" +#include "Identity.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Network.hpp" + +namespace ZeroTier { + +int CertificateOfOwnership::verify(const RuntimeEnvironment *RR,void *tPtr) const +{ + if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) + return -1; + const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); + if (!id) { + RR->sw->requestWhois(tPtr,_signedBy); + return 1; + } + try { + Buffer<(sizeof(CertificateOfOwnership) + 64)> tmp; + this->serialize(tmp,true); + return (id.verify(tmp.data(),tmp.size(),_signature) ? 0 : -1); + } catch ( ... ) { + return -1; + } +} + +bool CertificateOfOwnership::_owns(const CertificateOfOwnership::Thing &t,const void *v,unsigned int l) const +{ + for(unsigned int i=0,j=_thingCount;i(v)[k] != _thingValues[i][k]) + break; + ++k; + } + if (k == l) + return true; + } + } + return false; +} + +} // namespace ZeroTier diff --git a/node/CertificateOfOwnership.hpp b/node/CertificateOfOwnership.hpp new file mode 100644 index 0000000..93be64d --- /dev/null +++ b/node/CertificateOfOwnership.hpp @@ -0,0 +1,237 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_CERTIFICATEOFOWNERSHIP_HPP +#define ZT_CERTIFICATEOFOWNERSHIP_HPP + +#include +#include +#include +#include + +#include "Constants.hpp" +#include "Credential.hpp" +#include "C25519.hpp" +#include "Address.hpp" +#include "Identity.hpp" +#include "Buffer.hpp" +#include "InetAddress.hpp" +#include "MAC.hpp" + +// Max things per CertificateOfOwnership +#define ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS 16 + +// Maximum size of a thing's value field in bytes +#define ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE 16 + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Certificate indicating ownership of a network identifier + */ +class CertificateOfOwnership : public Credential +{ +public: + static inline Credential::Type credentialType() { return Credential::CREDENTIAL_TYPE_COO; } + + enum Thing + { + THING_NULL = 0, + THING_MAC_ADDRESS = 1, + THING_IPV4_ADDRESS = 2, + THING_IPV6_ADDRESS = 3 + }; + + CertificateOfOwnership() + { + memset(this,0,sizeof(CertificateOfOwnership)); + } + + CertificateOfOwnership(const uint64_t nwid,const uint64_t ts,const Address &issuedTo,const uint32_t id) : + _networkId(nwid), + _ts(ts), + _flags(0), + _id(id), + _thingCount(0), + _issuedTo(issuedTo) + { + } + + inline uint64_t networkId() const { return _networkId; } + inline uint64_t timestamp() const { return _ts; } + inline uint32_t id() const { return _id; } + inline unsigned int thingCount() const { return (unsigned int)_thingCount; } + + inline Thing thingType(const unsigned int i) const { return (Thing)_thingTypes[i]; } + inline const uint8_t *thingValue(const unsigned int i) const { return _thingValues[i]; } + + inline const Address &issuedTo() const { return _issuedTo; } + + inline bool owns(const InetAddress &ip) const + { + if (ip.ss_family == AF_INET) + return this->_owns(THING_IPV4_ADDRESS,&(reinterpret_cast(&ip)->sin_addr.s_addr),4); + if (ip.ss_family == AF_INET6) + return this->_owns(THING_IPV6_ADDRESS,reinterpret_cast(&ip)->sin6_addr.s6_addr,16); + return false; + } + + inline bool owns(const MAC &mac) const + { + uint8_t tmp[6]; + mac.copyTo(tmp,6); + return this->_owns(THING_MAC_ADDRESS,tmp,6); + } + + inline void addThing(const InetAddress &ip) + { + if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) return; + if (ip.ss_family == AF_INET) { + _thingTypes[_thingCount] = THING_IPV4_ADDRESS; + memcpy(_thingValues[_thingCount],&(reinterpret_cast(&ip)->sin_addr.s_addr),4); + ++_thingCount; + } else if (ip.ss_family == AF_INET6) { + _thingTypes[_thingCount] = THING_IPV6_ADDRESS; + memcpy(_thingValues[_thingCount],reinterpret_cast(&ip)->sin6_addr.s6_addr,16); + ++_thingCount; + } + } + + inline void addThing(const MAC &mac) + { + if (_thingCount >= ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS) return; + _thingTypes[_thingCount] = THING_MAC_ADDRESS; + mac.copyTo(_thingValues[_thingCount],6); + ++_thingCount; + } + + /** + * @param signer Signing identity, must have private key + * @return True if signature was successful + */ + inline bool sign(const Identity &signer) + { + if (signer.hasPrivate()) { + Buffer tmp; + _signedBy = signer.address(); + this->serialize(tmp,true); + _signature = signer.sign(tmp.data(),tmp.size()); + return true; + } + return false; + } + + /** + * @param RR Runtime environment to allow identity lookup for signedBy + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature + */ + int verify(const RuntimeEnvironment *RR,void *tPtr) const; + + template + inline void serialize(Buffer &b,const bool forSign = false) const + { + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + + b.append(_networkId); + b.append(_ts); + b.append(_flags); + b.append(_id); + b.append((uint16_t)_thingCount); + for(unsigned int i=0,j=_thingCount;i + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + unsigned int p = startAt; + + memset(this,0,sizeof(CertificateOfOwnership)); + + _networkId = b.template at(p); p += 8; + _ts = b.template at(p); p += 8; + _flags = b.template at(p); p += 8; + _id = b.template at(p); p += 4; + _thingCount = b.template at(p); p += 2; + for(unsigned int i=0,j=_thingCount;i(p) != ZT_C25519_SIGNATURE_LEN) + throw std::runtime_error("invalid signature length"); + p += 2; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + } else { + p += 2 + b.template at(p); + } + + p += 2 + b.template at(p); + if (p > b.size()) + throw std::runtime_error("extended field overflow"); + + return (p - startAt); + } + + // Provides natural sort order by ID + inline bool operator<(const CertificateOfOwnership &coo) const { return (_id < coo._id); } + + inline bool operator==(const CertificateOfOwnership &coo) const { return (memcmp(this,&coo,sizeof(CertificateOfOwnership)) == 0); } + inline bool operator!=(const CertificateOfOwnership &coo) const { return (memcmp(this,&coo,sizeof(CertificateOfOwnership)) != 0); } + +private: + bool _owns(const Thing &t,const void *v,unsigned int l) const; + + uint64_t _networkId; + uint64_t _ts; + uint64_t _flags; + uint32_t _id; + uint16_t _thingCount; + uint8_t _thingTypes[ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS]; + uint8_t _thingValues[ZT_CERTIFICATEOFOWNERSHIP_MAX_THINGS][ZT_CERTIFICATEOFOWNERSHIP_MAX_THING_VALUE_SIZE]; + Address _issuedTo; + Address _signedBy; + C25519::Signature _signature; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/CertificateOfRepresentation.hpp b/node/CertificateOfRepresentation.hpp new file mode 100644 index 0000000..710ee57 --- /dev/null +++ b/node/CertificateOfRepresentation.hpp @@ -0,0 +1,180 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_CERTIFICATEOFREPRESENTATION_HPP +#define ZT_CERTIFICATEOFREPRESENTATION_HPP + +#include "Constants.hpp" +#include "Credential.hpp" +#include "Address.hpp" +#include "C25519.hpp" +#include "Identity.hpp" +#include "Buffer.hpp" + +/** + * Maximum number of addresses allowed in a COR + */ +#define ZT_CERTIFICATEOFREPRESENTATION_MAX_ADDRESSES ZT_MAX_UPSTREAMS + +namespace ZeroTier { + +/** + * A signed enumeration of a node's roots (planet and moons) + * + * This is sent as part of HELLO and attests to which roots a node trusts + * to represent it on the network. Federated roots (moons) can send these + * further upstream to tell global roots which nodes they represent, making + * them reachable via federated roots if they are not reachable directly. + * + * As of 1.2.0 this is sent but not used. Right now nodes still always + * announce to planetary roots no matter what. In the future this can be + * used to implement even better fault tolerance for federation for the + * no roots are reachable case as well as a "privacy mode" where federated + * roots can shield nodes entirely and p2p connectivity behind them can + * be disabled. This will be desirable for a number of use cases. + */ +class CertificateOfRepresentation : public Credential +{ +public: + static inline Credential::Type credentialType() { return Credential::CREDENTIAL_TYPE_COR; } + + CertificateOfRepresentation() + { + memset(this,0,sizeof(CertificateOfRepresentation)); + } + + inline uint32_t id() const { return 0; } + inline uint64_t timestamp() const { return _timestamp; } + inline const Address &representative(const unsigned int i) const { return _reps[i]; } + inline unsigned int repCount() const { return _repCount; } + + inline void clear() + { + memset(this,0,sizeof(CertificateOfRepresentation)); + } + + /** + * Add a representative if space remains + * + * @param r Representative to add + * @return True if representative was added + */ + inline bool addRepresentative(const Address &r) + { + if (_repCount < ZT_CERTIFICATEOFREPRESENTATION_MAX_ADDRESSES) { + _reps[_repCount++] = r; + return true; + } + return false; + } + + /** + * Sign this COR with my identity + * + * @param myIdentity This node's identity + * @param ts COR timestamp for establishing new vs. old + */ + inline void sign(const Identity &myIdentity,const uint64_t ts) + { + _timestamp = ts; + Buffer tmp; + this->serialize(tmp,true); + _signature = myIdentity.sign(tmp.data(),tmp.size()); + } + + /** + * Verify this COR's signature + * + * @param senderIdentity Identity of sender of COR + * @return True if COR is valid + */ + inline bool verify(const Identity &senderIdentity) + { + try { + Buffer tmp; + this->serialize(tmp,true); + return senderIdentity.verify(tmp.data(),tmp.size(),_signature.data,ZT_C25519_SIGNATURE_LEN); + } catch ( ... ) { + return false; + } + } + + template + inline void serialize(Buffer &b,const bool forSign = false) const + { + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + + b.append((uint64_t)_timestamp); + b.append((uint16_t)_repCount); + for(unsigned int i=0;i<_repCount;++i) + _reps[i].appendTo(b); + + if (!forSign) { + b.append((uint8_t)1); // 1 == Ed25519 signature + b.append((uint16_t)ZT_C25519_SIGNATURE_LEN); + b.append(_signature.data,ZT_C25519_SIGNATURE_LEN); + } + + b.append((uint16_t)0); // size of any additional fields, currently 0 + + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } + + template + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + clear(); + + unsigned int p = startAt; + + _timestamp = b.template at(p); p += 8; + const unsigned int rc = b.template at(p); p += 2; + for(unsigned int i=0;i ZT_CERTIFICATEOFREPRESENTATION_MAX_ADDRESSES) ? ZT_CERTIFICATEOFREPRESENTATION_MAX_ADDRESSES : rc; + + if (b[p++] == 1) { + if (b.template at(p) == ZT_C25519_SIGNATURE_LEN) { + p += 2; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); + p += ZT_C25519_SIGNATURE_LEN; + } else throw std::runtime_error("invalid signature"); + } else { + p += 2 + b.template at(p); + } + + p += 2 + b.template at(p); + if (p > b.size()) + throw std::runtime_error("extended field overflow"); + + return (p - startAt); + } + +private: + uint64_t _timestamp; + Address _reps[ZT_CERTIFICATEOFREPRESENTATION_MAX_ADDRESSES]; + unsigned int _repCount; + C25519::Signature _signature; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Cluster.cpp b/node/Cluster.cpp new file mode 100644 index 0000000..54206f9 --- /dev/null +++ b/node/Cluster.cpp @@ -0,0 +1,1034 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifdef ZT_ENABLE_CLUSTER + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../version.h" + +#include "Cluster.hpp" +#include "RuntimeEnvironment.hpp" +#include "MulticastGroup.hpp" +#include "CertificateOfMembership.hpp" +#include "Salsa20.hpp" +#include "Poly1305.hpp" +#include "Identity.hpp" +#include "Topology.hpp" +#include "Packet.hpp" +#include "Switch.hpp" +#include "Node.hpp" +#include "Network.hpp" +#include "Array.hpp" + +namespace ZeroTier { + +static inline double _dist3d(int x1,int y1,int z1,int x2,int y2,int z2) + throw() +{ + double dx = ((double)x2 - (double)x1); + double dy = ((double)y2 - (double)y1); + double dz = ((double)z2 - (double)z1); + return sqrt((dx * dx) + (dy * dy) + (dz * dz)); +} + +// An entry in _ClusterSendQueue +struct _ClusterSendQueueEntry +{ + uint64_t timestamp; + Address fromPeerAddress; + Address toPeerAddress; + // if we ever support larger transport MTUs this must be increased + unsigned char data[ZT_CLUSTER_SEND_QUEUE_DATA_MAX]; + unsigned int len; + bool unite; +}; + +// A multi-index map with entry memory pooling -- this allows our queue to +// be O(log(N)) and is complex enough that it makes the code a lot cleaner +// to break it out from Cluster. +class _ClusterSendQueue +{ +public: + _ClusterSendQueue() : + _poolCount(0) {} + ~_ClusterSendQueue() {} // memory is automatically freed when _chunks is destroyed + + inline void enqueue(uint64_t now,const Address &from,const Address &to,const void *data,unsigned int len,bool unite) + { + if (len > ZT_CLUSTER_SEND_QUEUE_DATA_MAX) + return; + + Mutex::Lock _l(_lock); + + // Delete oldest queue entry for this sender if this enqueue() would take them over the per-sender limit + { + std::set< std::pair >::iterator qi(_bySrc.lower_bound(std::pair(from,(_ClusterSendQueueEntry *)0))); + std::set< std::pair >::iterator oldest(qi); + unsigned long countForSender = 0; + while ((qi != _bySrc.end())&&(qi->first == from)) { + if (qi->second->timestamp < oldest->second->timestamp) + oldest = qi; + ++countForSender; + ++qi; + } + if (countForSender >= ZT_CLUSTER_MAX_QUEUE_PER_SENDER) { + _byDest.erase(std::pair(oldest->second->toPeerAddress,oldest->second)); + _pool[_poolCount++] = oldest->second; + _bySrc.erase(oldest); + } + } + + _ClusterSendQueueEntry *e; + if (_poolCount > 0) { + e = _pool[--_poolCount]; + } else { + if (_chunks.size() >= ZT_CLUSTER_MAX_QUEUE_CHUNKS) + return; // queue is totally full! + _chunks.push_back(Array<_ClusterSendQueueEntry,ZT_CLUSTER_QUEUE_CHUNK_SIZE>()); + e = &(_chunks.back().data[0]); + for(unsigned int i=1;itimestamp = now; + e->fromPeerAddress = from; + e->toPeerAddress = to; + memcpy(e->data,data,len); + e->len = len; + e->unite = unite; + + _bySrc.insert(std::pair(from,e)); + _byDest.insert(std::pair(to,e)); + } + + inline void expire(uint64_t now) + { + Mutex::Lock _l(_lock); + for(std::set< std::pair >::iterator qi(_bySrc.begin());qi!=_bySrc.end();) { + if ((now - qi->second->timestamp) > ZT_CLUSTER_QUEUE_EXPIRATION) { + _byDest.erase(std::pair(qi->second->toPeerAddress,qi->second)); + _pool[_poolCount++] = qi->second; + _bySrc.erase(qi++); + } else ++qi; + } + } + + /** + * Get and dequeue entries for a given destination address + * + * After use these entries must be returned with returnToPool()! + * + * @param dest Destination address + * @param results Array to fill with results + * @param maxResults Size of results[] in pointers + * @return Number of actual results returned + */ + inline unsigned int getByDest(const Address &dest,_ClusterSendQueueEntry **results,unsigned int maxResults) + { + unsigned int count = 0; + Mutex::Lock _l(_lock); + std::set< std::pair >::iterator qi(_byDest.lower_bound(std::pair(dest,(_ClusterSendQueueEntry *)0))); + while ((qi != _byDest.end())&&(qi->first == dest)) { + _bySrc.erase(std::pair(qi->second->fromPeerAddress,qi->second)); + results[count++] = qi->second; + if (count == maxResults) + break; + _byDest.erase(qi++); + } + return count; + } + + /** + * Return entries to pool after use + * + * @param entries Array of entries + * @param count Number of entries + */ + inline void returnToPool(_ClusterSendQueueEntry **entries,unsigned int count) + { + Mutex::Lock _l(_lock); + for(unsigned int i=0;i > _chunks; + _ClusterSendQueueEntry *_pool[ZT_CLUSTER_QUEUE_CHUNK_SIZE * ZT_CLUSTER_MAX_QUEUE_CHUNKS]; + unsigned long _poolCount; + std::set< std::pair > _bySrc; + std::set< std::pair > _byDest; + Mutex _lock; +}; + +Cluster::Cluster( + const RuntimeEnvironment *renv, + uint16_t id, + const std::vector &zeroTierPhysicalEndpoints, + int32_t x, + int32_t y, + int32_t z, + void (*sendFunction)(void *,unsigned int,const void *,unsigned int), + void *sendFunctionArg, + int (*addressToLocationFunction)(void *,const struct sockaddr_storage *,int *,int *,int *), + void *addressToLocationFunctionArg) : + RR(renv), + _sendQueue(new _ClusterSendQueue()), + _sendFunction(sendFunction), + _sendFunctionArg(sendFunctionArg), + _addressToLocationFunction(addressToLocationFunction), + _addressToLocationFunctionArg(addressToLocationFunctionArg), + _x(x), + _y(y), + _z(z), + _id(id), + _zeroTierPhysicalEndpoints(zeroTierPhysicalEndpoints), + _members(new _Member[ZT_CLUSTER_MAX_MEMBERS]), + _lastFlushed(0), + _lastCleanedRemotePeers(0), + _lastCleanedQueue(0) +{ + uint16_t stmp[ZT_SHA512_DIGEST_LEN / sizeof(uint16_t)]; + + // Generate master secret by hashing the secret from our Identity key pair + RR->identity.sha512PrivateKey(_masterSecret); + + // Generate our inbound message key, which is the master secret XORed with our ID and hashed twice + memcpy(stmp,_masterSecret,sizeof(stmp)); + stmp[0] ^= Utils::hton(id); + SHA512::hash(stmp,stmp,sizeof(stmp)); + SHA512::hash(stmp,stmp,sizeof(stmp)); + memcpy(_key,stmp,sizeof(_key)); + Utils::burn(stmp,sizeof(stmp)); +} + +Cluster::~Cluster() +{ + Utils::burn(_masterSecret,sizeof(_masterSecret)); + Utils::burn(_key,sizeof(_key)); + delete [] _members; + delete _sendQueue; +} + +void Cluster::handleIncomingStateMessage(const void *msg,unsigned int len) +{ + Buffer dmsg; + { + // FORMAT: <[16] iv><[8] MAC><... data> + if ((len < 24)||(len > ZT_CLUSTER_MAX_MESSAGE_LENGTH)) + return; + + // 16-byte IV: first 8 bytes XORed with key, last 8 bytes used as Salsa20 64-bit IV + char keytmp[32]; + memcpy(keytmp,_key,32); + for(int i=0;i<8;++i) + keytmp[i] ^= reinterpret_cast(msg)[i]; + Salsa20 s20(keytmp,256,reinterpret_cast(msg) + 8); + Utils::burn(keytmp,sizeof(keytmp)); + + // One-time-use Poly1305 key from first 32 bytes of Salsa20 keystream (as per DJB/NaCl "standard") + char polykey[ZT_POLY1305_KEY_LEN]; + memset(polykey,0,sizeof(polykey)); + s20.crypt12(polykey,polykey,sizeof(polykey)); + + // Compute 16-byte MAC + char mac[ZT_POLY1305_MAC_LEN]; + Poly1305::compute(mac,reinterpret_cast(msg) + 24,len - 24,polykey); + + // Check first 8 bytes of MAC against 64-bit MAC in stream + if (!Utils::secureEq(mac,reinterpret_cast(msg) + 16,8)) + return; + + // Decrypt! + dmsg.setSize(len - 24); + s20.crypt12(reinterpret_cast(msg) + 24,const_cast(dmsg.data()),dmsg.size()); + } + + if (dmsg.size() < 4) + return; + const uint16_t fromMemberId = dmsg.at(0); + unsigned int ptr = 2; + if (fromMemberId == _id) // sanity check: we don't talk to ourselves + return; + const uint16_t toMemberId = dmsg.at(ptr); + ptr += 2; + if (toMemberId != _id) // sanity check: message not for us? + return; + + { // make sure sender is actually considered a member + Mutex::Lock _l3(_memberIds_m); + if (std::find(_memberIds.begin(),_memberIds.end(),fromMemberId) == _memberIds.end()) + return; + } + + try { + while (ptr < dmsg.size()) { + const unsigned int mlen = dmsg.at(ptr); ptr += 2; + const unsigned int nextPtr = ptr + mlen; + if (nextPtr > dmsg.size()) + break; + + int mtype = -1; + try { + switch((StateMessageType)(mtype = (int)dmsg[ptr++])) { + default: + break; + + case CLUSTER_MESSAGE_ALIVE: { + _Member &m = _members[fromMemberId]; + Mutex::Lock mlck(m.lock); + ptr += 7; // skip version stuff, not used yet + m.x = dmsg.at(ptr); ptr += 4; + m.y = dmsg.at(ptr); ptr += 4; + m.z = dmsg.at(ptr); ptr += 4; + ptr += 8; // skip local clock, not used + m.load = dmsg.at(ptr); ptr += 8; + m.peers = dmsg.at(ptr); ptr += 8; + ptr += 8; // skip flags, unused +#ifdef ZT_TRACE + std::string addrs; +#endif + unsigned int physicalAddressCount = dmsg[ptr++]; + m.zeroTierPhysicalEndpoints.clear(); + for(unsigned int i=0;i 0) + addrs.push_back(','); + addrs.append(m.zeroTierPhysicalEndpoints.back().toString()); + } +#endif + } +#ifdef ZT_TRACE + if ((RR->node->now() - m.lastReceivedAliveAnnouncement) >= ZT_CLUSTER_TIMEOUT) { + TRACE("[%u] I'm alive! peers close to %d,%d,%d can be redirected to: %s",(unsigned int)fromMemberId,m.x,m.y,m.z,addrs.c_str()); + } +#endif + m.lastReceivedAliveAnnouncement = RR->node->now(); + } break; + + case CLUSTER_MESSAGE_HAVE_PEER: { + Identity id; + ptr += id.deserialize(dmsg,ptr); + if (id) { + { + Mutex::Lock _l(_remotePeers_m); + _RemotePeer &rp = _remotePeers[std::pair(id.address(),(unsigned int)fromMemberId)]; + if (!rp.lastHavePeerReceived) { + RR->topology->saveIdentity((void *)0,id); + RR->identity.agree(id,rp.key,ZT_PEER_SECRET_KEY_LENGTH); + } + rp.lastHavePeerReceived = RR->node->now(); + } + + _ClusterSendQueueEntry *q[16384]; // 16384 is "tons" + unsigned int qc = _sendQueue->getByDest(id.address(),q,16384); + for(unsigned int i=0;irelayViaCluster(q[i]->fromPeerAddress,q[i]->toPeerAddress,q[i]->data,q[i]->len,q[i]->unite); + _sendQueue->returnToPool(q,qc); + + TRACE("[%u] has %s (retried %u queued sends)",(unsigned int)fromMemberId,id.address().toString().c_str(),qc); + } + } break; + + case CLUSTER_MESSAGE_WANT_PEER: { + const Address zeroTierAddress(dmsg.field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); ptr += ZT_ADDRESS_LENGTH; + SharedPtr peer(RR->topology->getPeerNoCache(zeroTierAddress)); + if ( (peer) && (peer->hasLocalClusterOptimalPath(RR->node->now())) ) { + Buffer<1024> buf; + peer->identity().serialize(buf); + Mutex::Lock _l2(_members[fromMemberId].lock); + _send(fromMemberId,CLUSTER_MESSAGE_HAVE_PEER,buf.data(),buf.size()); + } + } break; + + case CLUSTER_MESSAGE_REMOTE_PACKET: { + const unsigned int plen = dmsg.at(ptr); ptr += 2; + if (plen) { + Packet remotep(dmsg.field(ptr,plen),plen); ptr += plen; + //TRACE("remote %s from %s via %u (%u bytes)",Packet::verbString(remotep.verb()),remotep.source().toString().c_str(),fromMemberId,plen); + switch(remotep.verb()) { + case Packet::VERB_WHOIS: _doREMOTE_WHOIS(fromMemberId,remotep); break; + case Packet::VERB_MULTICAST_GATHER: _doREMOTE_MULTICAST_GATHER(fromMemberId,remotep); break; + default: break; // ignore things we don't care about across cluster + } + } + } break; + + case CLUSTER_MESSAGE_PROXY_UNITE: { + const Address localPeerAddress(dmsg.field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); ptr += ZT_ADDRESS_LENGTH; + const Address remotePeerAddress(dmsg.field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); ptr += ZT_ADDRESS_LENGTH; + const unsigned int numRemotePeerPaths = dmsg[ptr++]; + InetAddress remotePeerPaths[256]; // size is 8-bit, so 256 is max + for(unsigned int i=0;inode->now(); + SharedPtr localPeer(RR->topology->getPeerNoCache(localPeerAddress)); + if ((localPeer)&&(numRemotePeerPaths > 0)) { + InetAddress bestLocalV4,bestLocalV6; + localPeer->getRendezvousAddresses(now,bestLocalV4,bestLocalV6); + + InetAddress bestRemoteV4,bestRemoteV6; + for(unsigned int i=0;iidentity.address(),Packet::VERB_RENDEZVOUS); + rendezvousForLocal.append((uint8_t)0); + remotePeerAddress.appendTo(rendezvousForLocal); + + Buffer<2048> rendezvousForRemote; + remotePeerAddress.appendTo(rendezvousForRemote); + rendezvousForRemote.append((uint8_t)Packet::VERB_RENDEZVOUS); + rendezvousForRemote.addSize(2); // space for actual packet payload length + rendezvousForRemote.append((uint8_t)0); // flags == 0 + localPeerAddress.appendTo(rendezvousForRemote); + + bool haveMatch = false; + if ((bestLocalV6)&&(bestRemoteV6)) { + haveMatch = true; + + rendezvousForLocal.append((uint16_t)bestRemoteV6.port()); + rendezvousForLocal.append((uint8_t)16); + rendezvousForLocal.append(bestRemoteV6.rawIpData(),16); + + rendezvousForRemote.append((uint16_t)bestLocalV6.port()); + rendezvousForRemote.append((uint8_t)16); + rendezvousForRemote.append(bestLocalV6.rawIpData(),16); + rendezvousForRemote.setAt(ZT_ADDRESS_LENGTH + 1,(uint16_t)(9 + 16)); + } else if ((bestLocalV4)&&(bestRemoteV4)) { + haveMatch = true; + + rendezvousForLocal.append((uint16_t)bestRemoteV4.port()); + rendezvousForLocal.append((uint8_t)4); + rendezvousForLocal.append(bestRemoteV4.rawIpData(),4); + + rendezvousForRemote.append((uint16_t)bestLocalV4.port()); + rendezvousForRemote.append((uint8_t)4); + rendezvousForRemote.append(bestLocalV4.rawIpData(),4); + rendezvousForRemote.setAt(ZT_ADDRESS_LENGTH + 1,(uint16_t)(9 + 4)); + } + + if (haveMatch) { + { + Mutex::Lock _l2(_members[fromMemberId].lock); + _send(fromMemberId,CLUSTER_MESSAGE_PROXY_SEND,rendezvousForRemote.data(),rendezvousForRemote.size()); + } + RR->sw->send((void *)0,rendezvousForLocal,true); + } + } + } break; + + case CLUSTER_MESSAGE_PROXY_SEND: { + const Address rcpt(dmsg.field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); ptr += ZT_ADDRESS_LENGTH; + const Packet::Verb verb = (Packet::Verb)dmsg[ptr++]; + const unsigned int len = dmsg.at(ptr); ptr += 2; + Packet outp(rcpt,RR->identity.address(),verb); + outp.append(dmsg.field(ptr,len),len); ptr += len; + RR->sw->send((void *)0,outp,true); + //TRACE("[%u] proxy send %s to %s length %u",(unsigned int)fromMemberId,Packet::verbString(verb),rcpt.toString().c_str(),len); + } break; + + case CLUSTER_MESSAGE_NETWORK_CONFIG: { + const SharedPtr network(RR->node->network(dmsg.at(ptr))); + if (network) { + // Copy into a Packet just to conform to Network API. Eventually + // will want to refactor. + network->handleConfigChunk((void *)0,0,Address(),Buffer(dmsg),ptr); + } + } break; + } + } catch ( ... ) { + TRACE("invalid message of size %u type %d (inner decode), discarding",mlen,mtype); + // drop invalids + } + + ptr = nextPtr; + } + } catch ( ... ) { + TRACE("invalid message (outer loop), discarding"); + // drop invalids + } +} + +void Cluster::broadcastHavePeer(const Identity &id) +{ + Buffer<1024> buf; + id.serialize(buf); + Mutex::Lock _l(_memberIds_m); + for(std::vector::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) { + Mutex::Lock _l2(_members[*mid].lock); + _send(*mid,CLUSTER_MESSAGE_HAVE_PEER,buf.data(),buf.size()); + } +} + +void Cluster::broadcastNetworkConfigChunk(const void *chunk,unsigned int len) +{ + Mutex::Lock _l(_memberIds_m); + for(std::vector::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) { + Mutex::Lock _l2(_members[*mid].lock); + _send(*mid,CLUSTER_MESSAGE_NETWORK_CONFIG,chunk,len); + } +} + +int Cluster::checkSendViaCluster(const Address &toPeerAddress,uint64_t &mostRecentTs,void *peerSecret) +{ + const uint64_t now = RR->node->now(); + mostRecentTs = 0; + int mostRecentMemberId = -1; + { + Mutex::Lock _l2(_remotePeers_m); + std::map< std::pair,_RemotePeer >::const_iterator rpe(_remotePeers.lower_bound(std::pair(toPeerAddress,0))); + for(;;) { + if ((rpe == _remotePeers.end())||(rpe->first.first != toPeerAddress)) + break; + else if (rpe->second.lastHavePeerReceived > mostRecentTs) { + mostRecentTs = rpe->second.lastHavePeerReceived; + memcpy(peerSecret,rpe->second.key,ZT_PEER_SECRET_KEY_LENGTH); + mostRecentMemberId = (int)rpe->first.second; + } + ++rpe; + } + } + + const uint64_t ageOfMostRecentHavePeerAnnouncement = now - mostRecentTs; + if (ageOfMostRecentHavePeerAnnouncement >= (ZT_PEER_ACTIVITY_TIMEOUT / 3)) { + if (ageOfMostRecentHavePeerAnnouncement >= ZT_PEER_ACTIVITY_TIMEOUT) + mostRecentMemberId = -1; + + bool sendWantPeer = true; + { + Mutex::Lock _l(_remotePeers_m); + _RemotePeer &rp = _remotePeers[std::pair(toPeerAddress,(unsigned int)_id)]; + if ((now - rp.lastSentWantPeer) >= ZT_CLUSTER_WANT_PEER_EVERY) { + rp.lastSentWantPeer = now; + } else { + sendWantPeer = false; // don't flood WANT_PEER + } + } + if (sendWantPeer) { + char tmp[ZT_ADDRESS_LENGTH]; + toPeerAddress.copyTo(tmp,ZT_ADDRESS_LENGTH); + { + Mutex::Lock _l(_memberIds_m); + for(std::vector::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) { + Mutex::Lock _l2(_members[*mid].lock); + _send(*mid,CLUSTER_MESSAGE_WANT_PEER,tmp,ZT_ADDRESS_LENGTH); + } + } + } + } + + return mostRecentMemberId; +} + +bool Cluster::sendViaCluster(int mostRecentMemberId,const Address &toPeerAddress,const void *data,unsigned int len) +{ + if ((mostRecentMemberId < 0)||(mostRecentMemberId >= ZT_CLUSTER_MAX_MEMBERS)) // sanity check + return false; + Mutex::Lock _l2(_members[mostRecentMemberId].lock); + for(std::vector::const_iterator i1(_zeroTierPhysicalEndpoints.begin());i1!=_zeroTierPhysicalEndpoints.end();++i1) { + for(std::vector::const_iterator i2(_members[mostRecentMemberId].zeroTierPhysicalEndpoints.begin());i2!=_members[mostRecentMemberId].zeroTierPhysicalEndpoints.end();++i2) { + if (i1->ss_family == i2->ss_family) { + TRACE("sendViaCluster sending %u bytes to %s by way of %u (%s->%s)",len,toPeerAddress.toString().c_str(),(unsigned int)mostRecentMemberId,i1->toString().c_str(),i2->toString().c_str()); + RR->node->putPacket((void *)0,*i1,*i2,data,len); + return true; + } + } + } + return false; +} + +void Cluster::relayViaCluster(const Address &fromPeerAddress,const Address &toPeerAddress,const void *data,unsigned int len,bool unite) +{ + if (len > ZT_PROTO_MAX_PACKET_LENGTH) // sanity check + return; + + const uint64_t now = RR->node->now(); + + uint64_t mostRecentTs = 0; + int mostRecentMemberId = -1; + { + Mutex::Lock _l2(_remotePeers_m); + std::map< std::pair,_RemotePeer >::const_iterator rpe(_remotePeers.lower_bound(std::pair(toPeerAddress,0))); + for(;;) { + if ((rpe == _remotePeers.end())||(rpe->first.first != toPeerAddress)) + break; + else if (rpe->second.lastHavePeerReceived > mostRecentTs) { + mostRecentTs = rpe->second.lastHavePeerReceived; + mostRecentMemberId = (int)rpe->first.second; + } + ++rpe; + } + } + + const uint64_t ageOfMostRecentHavePeerAnnouncement = now - mostRecentTs; + if (ageOfMostRecentHavePeerAnnouncement >= (ZT_PEER_ACTIVITY_TIMEOUT / 3)) { + // Enqueue and wait if peer seems alive, but do WANT_PEER to refresh homing + const bool enqueueAndWait = ((ageOfMostRecentHavePeerAnnouncement >= ZT_PEER_ACTIVITY_TIMEOUT)||(mostRecentMemberId < 0)); + + // Poll everyone with WANT_PEER if the age of our most recent entry is + // approaching expiration (or has expired, or does not exist). + bool sendWantPeer = true; + { + Mutex::Lock _l(_remotePeers_m); + _RemotePeer &rp = _remotePeers[std::pair(toPeerAddress,(unsigned int)_id)]; + if ((now - rp.lastSentWantPeer) >= ZT_CLUSTER_WANT_PEER_EVERY) { + rp.lastSentWantPeer = now; + } else { + sendWantPeer = false; // don't flood WANT_PEER + } + } + if (sendWantPeer) { + char tmp[ZT_ADDRESS_LENGTH]; + toPeerAddress.copyTo(tmp,ZT_ADDRESS_LENGTH); + { + Mutex::Lock _l(_memberIds_m); + for(std::vector::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) { + Mutex::Lock _l2(_members[*mid].lock); + _send(*mid,CLUSTER_MESSAGE_WANT_PEER,tmp,ZT_ADDRESS_LENGTH); + } + } + } + + // If there isn't a good place to send via, then enqueue this for retrying + // later and return after having broadcasted a WANT_PEER. + if (enqueueAndWait) { + TRACE("relayViaCluster %s -> %s enqueueing to wait for HAVE_PEER",fromPeerAddress.toString().c_str(),toPeerAddress.toString().c_str()); + _sendQueue->enqueue(now,fromPeerAddress,toPeerAddress,data,len,unite); + return; + } + } + + if (mostRecentMemberId >= 0) { + Buffer<1024> buf; + if (unite) { + InetAddress v4,v6; + if (fromPeerAddress) { + SharedPtr fromPeer(RR->topology->getPeerNoCache(fromPeerAddress)); + if (fromPeer) + fromPeer->getRendezvousAddresses(now,v4,v6); + } + uint8_t addrCount = 0; + if (v4) + ++addrCount; + if (v6) + ++addrCount; + if (addrCount) { + toPeerAddress.appendTo(buf); + fromPeerAddress.appendTo(buf); + buf.append(addrCount); + if (v4) + v4.serialize(buf); + if (v6) + v6.serialize(buf); + } + } + + { + Mutex::Lock _l2(_members[mostRecentMemberId].lock); + if (buf.size() > 0) + _send(mostRecentMemberId,CLUSTER_MESSAGE_PROXY_UNITE,buf.data(),buf.size()); + + for(std::vector::const_iterator i1(_zeroTierPhysicalEndpoints.begin());i1!=_zeroTierPhysicalEndpoints.end();++i1) { + for(std::vector::const_iterator i2(_members[mostRecentMemberId].zeroTierPhysicalEndpoints.begin());i2!=_members[mostRecentMemberId].zeroTierPhysicalEndpoints.end();++i2) { + if (i1->ss_family == i2->ss_family) { + TRACE("relayViaCluster relaying %u bytes from %s to %s by way of %u (%s->%s)",len,fromPeerAddress.toString().c_str(),toPeerAddress.toString().c_str(),(unsigned int)mostRecentMemberId,i1->toString().c_str(),i2->toString().c_str()); + RR->node->putPacket((void *)0,*i1,*i2,data,len); + return; + } + } + } + + TRACE("relayViaCluster relaying %u bytes from %s to %s by way of %u failed: no common endpoints with the same address family!",len,fromPeerAddress.toString().c_str(),toPeerAddress.toString().c_str(),(unsigned int)mostRecentMemberId); + } + } +} + +void Cluster::sendDistributedQuery(const Packet &pkt) +{ + Buffer<4096> buf; + buf.append((uint16_t)pkt.size()); + buf.append(pkt.data(),pkt.size()); + Mutex::Lock _l(_memberIds_m); + for(std::vector::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) { + Mutex::Lock _l2(_members[*mid].lock); + _send(*mid,CLUSTER_MESSAGE_REMOTE_PACKET,buf.data(),buf.size()); + } +} + +void Cluster::doPeriodicTasks() +{ + const uint64_t now = RR->node->now(); + + if ((now - _lastFlushed) >= ZT_CLUSTER_FLUSH_PERIOD) { + _lastFlushed = now; + + Mutex::Lock _l(_memberIds_m); + for(std::vector::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) { + Mutex::Lock _l2(_members[*mid].lock); + + if ((now - _members[*mid].lastAnnouncedAliveTo) >= ((ZT_CLUSTER_TIMEOUT / 2) - 1000)) { + _members[*mid].lastAnnouncedAliveTo = now; + + Buffer<2048> alive; + alive.append((uint16_t)ZEROTIER_ONE_VERSION_MAJOR); + alive.append((uint16_t)ZEROTIER_ONE_VERSION_MINOR); + alive.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION); + alive.append((uint8_t)ZT_PROTO_VERSION); + if (_addressToLocationFunction) { + alive.append((int32_t)_x); + alive.append((int32_t)_y); + alive.append((int32_t)_z); + } else { + alive.append((int32_t)0); + alive.append((int32_t)0); + alive.append((int32_t)0); + } + alive.append((uint64_t)now); + alive.append((uint64_t)0); // TODO: compute and send load average + alive.append((uint64_t)RR->topology->countActive(now)); + alive.append((uint64_t)0); // unused/reserved flags + alive.append((uint8_t)_zeroTierPhysicalEndpoints.size()); + for(std::vector::const_iterator pe(_zeroTierPhysicalEndpoints.begin());pe!=_zeroTierPhysicalEndpoints.end();++pe) + pe->serialize(alive); + _send(*mid,CLUSTER_MESSAGE_ALIVE,alive.data(),alive.size()); + } + + _flush(*mid); + } + } + + if ((now - _lastCleanedRemotePeers) >= (ZT_PEER_ACTIVITY_TIMEOUT * 2)) { + _lastCleanedRemotePeers = now; + + Mutex::Lock _l(_remotePeers_m); + for(std::map< std::pair,_RemotePeer >::iterator rp(_remotePeers.begin());rp!=_remotePeers.end();) { + if ((now - rp->second.lastHavePeerReceived) >= ZT_PEER_ACTIVITY_TIMEOUT) + _remotePeers.erase(rp++); + else ++rp; + } + } + + if ((now - _lastCleanedQueue) >= ZT_CLUSTER_QUEUE_EXPIRATION) { + _lastCleanedQueue = now; + _sendQueue->expire(now); + } +} + +void Cluster::addMember(uint16_t memberId) +{ + if ((memberId >= ZT_CLUSTER_MAX_MEMBERS)||(memberId == _id)) + return; + + Mutex::Lock _l2(_members[memberId].lock); + + { + Mutex::Lock _l(_memberIds_m); + if (std::find(_memberIds.begin(),_memberIds.end(),memberId) != _memberIds.end()) + return; + _memberIds.push_back(memberId); + std::sort(_memberIds.begin(),_memberIds.end()); + } + + _members[memberId].clear(); + + // Generate this member's message key from the master and its ID + uint16_t stmp[ZT_SHA512_DIGEST_LEN / sizeof(uint16_t)]; + memcpy(stmp,_masterSecret,sizeof(stmp)); + stmp[0] ^= Utils::hton(memberId); + SHA512::hash(stmp,stmp,sizeof(stmp)); + SHA512::hash(stmp,stmp,sizeof(stmp)); + memcpy(_members[memberId].key,stmp,sizeof(_members[memberId].key)); + Utils::burn(stmp,sizeof(stmp)); + + // Prepare q + _members[memberId].q.clear(); + char iv[16]; + Utils::getSecureRandom(iv,16); + _members[memberId].q.append(iv,16); + _members[memberId].q.addSize(8); // room for MAC + _members[memberId].q.append((uint16_t)_id); + _members[memberId].q.append((uint16_t)memberId); +} + +void Cluster::removeMember(uint16_t memberId) +{ + Mutex::Lock _l(_memberIds_m); + std::vector newMemberIds; + for(std::vector::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) { + if (*mid != memberId) + newMemberIds.push_back(*mid); + } + _memberIds = newMemberIds; +} + +bool Cluster::findBetterEndpoint(InetAddress &redirectTo,const Address &peerAddress,const InetAddress &peerPhysicalAddress,bool offload) +{ + if (_addressToLocationFunction) { + // Pick based on location if it can be determined + int px = 0,py = 0,pz = 0; + if (_addressToLocationFunction(_addressToLocationFunctionArg,reinterpret_cast(&peerPhysicalAddress),&px,&py,&pz) == 0) { + TRACE("no geolocation data for %s",peerPhysicalAddress.toIpString().c_str()); + return false; + } + + // Find member closest to this peer + const uint64_t now = RR->node->now(); + std::vector best; + const double currentDistance = _dist3d(_x,_y,_z,px,py,pz); + double bestDistance = (offload ? 2147483648.0 : currentDistance); +#ifdef ZT_TRACE + unsigned int bestMember = _id; +#endif + { + Mutex::Lock _l(_memberIds_m); + for(std::vector::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) { + _Member &m = _members[*mid]; + Mutex::Lock _ml(m.lock); + + // Consider member if it's alive and has sent us a location and one or more physical endpoints to send peers to + if ( ((now - m.lastReceivedAliveAnnouncement) < ZT_CLUSTER_TIMEOUT) && ((m.x != 0)||(m.y != 0)||(m.z != 0)) && (m.zeroTierPhysicalEndpoints.size() > 0) ) { + const double mdist = _dist3d(m.x,m.y,m.z,px,py,pz); + if (mdist < bestDistance) { + bestDistance = mdist; +#ifdef ZT_TRACE + bestMember = *mid; +#endif + best = m.zeroTierPhysicalEndpoints; + } + } + } + } + + // Redirect to a closer member if it has a ZeroTier endpoint address in the same ss_family + for(std::vector::const_iterator a(best.begin());a!=best.end();++a) { + if (a->ss_family == peerPhysicalAddress.ss_family) { + TRACE("%s at [%d,%d,%d] is %f from us but %f from %u, can redirect to %s",peerAddress.toString().c_str(),px,py,pz,currentDistance,bestDistance,bestMember,a->toString().c_str()); + redirectTo = *a; + return true; + } + } + TRACE("%s at [%d,%d,%d] is %f from us, no better endpoints found",peerAddress.toString().c_str(),px,py,pz,currentDistance); + return false; + } else { + // TODO: pick based on load if no location info? + return false; + } +} + +bool Cluster::isClusterPeerFrontplane(const InetAddress &ip) const +{ + Mutex::Lock _l(_memberIds_m); + for(std::vector::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) { + Mutex::Lock _l2(_members[*mid].lock); + for(std::vector::const_iterator i2(_members[*mid].zeroTierPhysicalEndpoints.begin());i2!=_members[*mid].zeroTierPhysicalEndpoints.end();++i2) { + if (ip == *i2) + return true; + } + } + return false; +} + +void Cluster::status(ZT_ClusterStatus &status) const +{ + const uint64_t now = RR->node->now(); + memset(&status,0,sizeof(ZT_ClusterStatus)); + + status.myId = _id; + + { + ZT_ClusterMemberStatus *const s = &(status.members[status.clusterSize++]); + s->id = _id; + s->alive = 1; + s->x = _x; + s->y = _y; + s->z = _z; + s->load = 0; // TODO + s->peers = RR->topology->countActive(now); + for(std::vector::const_iterator ep(_zeroTierPhysicalEndpoints.begin());ep!=_zeroTierPhysicalEndpoints.end();++ep) { + if (s->numZeroTierPhysicalEndpoints >= ZT_CLUSTER_MAX_ZT_PHYSICAL_ADDRESSES) // sanity check + break; + memcpy(&(s->zeroTierPhysicalEndpoints[s->numZeroTierPhysicalEndpoints++]),&(*ep),sizeof(struct sockaddr_storage)); + } + } + + { + Mutex::Lock _l1(_memberIds_m); + for(std::vector::const_iterator mid(_memberIds.begin());mid!=_memberIds.end();++mid) { + if (status.clusterSize >= ZT_CLUSTER_MAX_MEMBERS) // sanity check + break; + + _Member &m = _members[*mid]; + Mutex::Lock ml(m.lock); + + ZT_ClusterMemberStatus *const s = &(status.members[status.clusterSize++]); + s->id = *mid; + s->msSinceLastHeartbeat = (unsigned int)std::min((uint64_t)(~((unsigned int)0)),(now - m.lastReceivedAliveAnnouncement)); + s->alive = (s->msSinceLastHeartbeat < ZT_CLUSTER_TIMEOUT) ? 1 : 0; + s->x = m.x; + s->y = m.y; + s->z = m.z; + s->load = m.load; + s->peers = m.peers; + for(std::vector::const_iterator ep(m.zeroTierPhysicalEndpoints.begin());ep!=m.zeroTierPhysicalEndpoints.end();++ep) { + if (s->numZeroTierPhysicalEndpoints >= ZT_CLUSTER_MAX_ZT_PHYSICAL_ADDRESSES) // sanity check + break; + memcpy(&(s->zeroTierPhysicalEndpoints[s->numZeroTierPhysicalEndpoints++]),&(*ep),sizeof(struct sockaddr_storage)); + } + } + } +} + +void Cluster::_send(uint16_t memberId,StateMessageType type,const void *msg,unsigned int len) +{ + if ((len + 3) > (ZT_CLUSTER_MAX_MESSAGE_LENGTH - (24 + 2 + 2))) // sanity check + return; + _Member &m = _members[memberId]; + // assumes m.lock is locked! + if ((m.q.size() + len + 3) > ZT_CLUSTER_MAX_MESSAGE_LENGTH) + _flush(memberId); + m.q.append((uint16_t)(len + 1)); + m.q.append((uint8_t)type); + m.q.append(msg,len); +} + +void Cluster::_flush(uint16_t memberId) +{ + _Member &m = _members[memberId]; + // assumes m.lock is locked! + if (m.q.size() > (24 + 2 + 2)) { // 16-byte IV + 8-byte MAC + 2 byte from-member-ID + 2 byte to-member-ID + // Create key from member's key and IV + char keytmp[32]; + memcpy(keytmp,m.key,32); + for(int i=0;i<8;++i) + keytmp[i] ^= m.q[i]; + Salsa20 s20(keytmp,256,m.q.field(8,8)); + Utils::burn(keytmp,sizeof(keytmp)); + + // One-time-use Poly1305 key from first 32 bytes of Salsa20 keystream (as per DJB/NaCl "standard") + char polykey[ZT_POLY1305_KEY_LEN]; + memset(polykey,0,sizeof(polykey)); + s20.crypt12(polykey,polykey,sizeof(polykey)); + + // Encrypt m.q in place + s20.crypt12(reinterpret_cast(m.q.data()) + 24,const_cast(reinterpret_cast(m.q.data())) + 24,m.q.size() - 24); + + // Add MAC for authentication (encrypt-then-MAC) + char mac[ZT_POLY1305_MAC_LEN]; + Poly1305::compute(mac,reinterpret_cast(m.q.data()) + 24,m.q.size() - 24,polykey); + memcpy(m.q.field(16,8),mac,8); + + // Send! + _sendFunction(_sendFunctionArg,memberId,m.q.data(),m.q.size()); + + // Prepare for more + m.q.clear(); + char iv[16]; + Utils::getSecureRandom(iv,16); + m.q.append(iv,16); + m.q.addSize(8); // room for MAC + m.q.append((uint16_t)_id); // from member ID + m.q.append((uint16_t)memberId); // to member ID + } +} + +void Cluster::_doREMOTE_WHOIS(uint64_t fromMemberId,const Packet &remotep) +{ + if (remotep.payloadLength() >= ZT_ADDRESS_LENGTH) { + Identity queried(RR->topology->getIdentity((void *)0,Address(remotep.payload(),ZT_ADDRESS_LENGTH))); + if (queried) { + Buffer<1024> routp; + remotep.source().appendTo(routp); + routp.append((uint8_t)Packet::VERB_OK); + routp.addSize(2); // space for length + routp.append((uint8_t)Packet::VERB_WHOIS); + routp.append(remotep.packetId()); + queried.serialize(routp); + routp.setAt(ZT_ADDRESS_LENGTH + 1,(uint16_t)(routp.size() - ZT_ADDRESS_LENGTH - 3)); + + TRACE("responding to remote WHOIS from %s @ %u with identity of %s",remotep.source().toString().c_str(),(unsigned int)fromMemberId,queried.address().toString().c_str()); + Mutex::Lock _l2(_members[fromMemberId].lock); + _send(fromMemberId,CLUSTER_MESSAGE_PROXY_SEND,routp.data(),routp.size()); + } + } +} + +void Cluster::_doREMOTE_MULTICAST_GATHER(uint64_t fromMemberId,const Packet &remotep) +{ + const uint64_t nwid = remotep.at(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID); + const MulticastGroup mg(MAC(remotep.field(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC,6),6),remotep.at(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_ADI)); + unsigned int gatherLimit = remotep.at(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_GATHER_LIMIT); + const Address remotePeerAddress(remotep.source()); + + if (gatherLimit) { + Buffer routp; + remotePeerAddress.appendTo(routp); + routp.append((uint8_t)Packet::VERB_OK); + routp.addSize(2); // space for length + routp.append((uint8_t)Packet::VERB_MULTICAST_GATHER); + routp.append(remotep.packetId()); + routp.append(nwid); + mg.mac().appendTo(routp); + routp.append((uint32_t)mg.adi()); + + if (gatherLimit > ((ZT_CLUSTER_MAX_MESSAGE_LENGTH - 80) / 5)) + gatherLimit = ((ZT_CLUSTER_MAX_MESSAGE_LENGTH - 80) / 5); + if (RR->mc->gather(remotePeerAddress,nwid,mg,routp,gatherLimit)) { + routp.setAt(ZT_ADDRESS_LENGTH + 1,(uint16_t)(routp.size() - ZT_ADDRESS_LENGTH - 3)); + + TRACE("responding to remote MULTICAST_GATHER from %s @ %u with %u bytes",remotePeerAddress.toString().c_str(),(unsigned int)fromMemberId,routp.size()); + Mutex::Lock _l2(_members[fromMemberId].lock); + _send(fromMemberId,CLUSTER_MESSAGE_PROXY_SEND,routp.data(),routp.size()); + } + } +} + +} // namespace ZeroTier + +#endif // ZT_ENABLE_CLUSTER diff --git a/node/Cluster.hpp b/node/Cluster.hpp new file mode 100644 index 0000000..08e32a9 --- /dev/null +++ b/node/Cluster.hpp @@ -0,0 +1,455 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_CLUSTER_HPP +#define ZT_CLUSTER_HPP + +#ifdef ZT_ENABLE_CLUSTER + +#include + +#include "Constants.hpp" +#include "../include/ZeroTierOne.h" +#include "Address.hpp" +#include "InetAddress.hpp" +#include "SHA512.hpp" +#include "Utils.hpp" +#include "Buffer.hpp" +#include "Mutex.hpp" +#include "SharedPtr.hpp" +#include "Hashtable.hpp" +#include "Packet.hpp" +#include "SharedPtr.hpp" + +/** + * Timeout for cluster members being considered "alive" + * + * A cluster member is considered dead and will no longer have peers + * redirected to it if we have not heard a heartbeat in this long. + */ +#define ZT_CLUSTER_TIMEOUT 5000 + +/** + * Desired period between doPeriodicTasks() in milliseconds + */ +#define ZT_CLUSTER_PERIODIC_TASK_PERIOD 20 + +/** + * How often to flush outgoing message queues (maximum interval) + */ +#define ZT_CLUSTER_FLUSH_PERIOD ZT_CLUSTER_PERIODIC_TASK_PERIOD + +/** + * Maximum number of queued outgoing packets per sender address + */ +#define ZT_CLUSTER_MAX_QUEUE_PER_SENDER 16 + +/** + * Expiration time for send queue entries + */ +#define ZT_CLUSTER_QUEUE_EXPIRATION 3000 + +/** + * Chunk size for allocating queue entries + * + * Queue entries are allocated in chunks of this many and are added to a pool. + * ZT_CLUSTER_MAX_QUEUE_GLOBAL must be evenly divisible by this. + */ +#define ZT_CLUSTER_QUEUE_CHUNK_SIZE 32 + +/** + * Maximum number of chunks to ever allocate + * + * This is a global sanity limit to prevent resource exhaustion attacks. It + * works out to about 600mb of RAM. You'll never see this on a normal edge + * node. We're unlikely to see this on a root server unless someone is DOSing + * us. In that case cluster relaying will be affected but other functions + * should continue to operate normally. + */ +#define ZT_CLUSTER_MAX_QUEUE_CHUNKS 8194 + +/** + * Max data per queue entry + */ +#define ZT_CLUSTER_SEND_QUEUE_DATA_MAX 1500 + +/** + * We won't send WANT_PEER to other members more than every (ms) per recipient + */ +#define ZT_CLUSTER_WANT_PEER_EVERY 1000 + +namespace ZeroTier { + +class RuntimeEnvironment; +class MulticastGroup; +class Peer; +class Identity; + +// Internal class implemented inside Cluster.cpp +class _ClusterSendQueue; + +/** + * Multi-homing cluster state replication and packet relaying + * + * Multi-homing means more than one node sharing the same ZeroTier identity. + * There is nothing in the protocol to prevent this, but to make it work well + * requires the devices sharing an identity to cooperate and share some + * information. + * + * There are three use cases we want to fulfill: + * + * (1) Multi-homing of root servers with handoff for efficient routing, + * HA, and load balancing across many commodity nodes. + * (2) Multi-homing of network controllers for the same reason. + * (3) Multi-homing of nodes on virtual networks, such as domain servers + * and other important endpoints. + * + * These use cases are in order of escalating difficulty. The initial + * version of Cluster is aimed at satisfying the first, though you are + * free to try #2 and #3. + */ +class Cluster +{ +public: + /** + * State message types + */ + enum StateMessageType + { + CLUSTER_MESSAGE_NOP = 0, + + /** + * This cluster member is alive: + * <[2] version minor> + * <[2] version major> + * <[2] version revision> + * <[1] protocol version> + * <[4] X location (signed 32-bit)> + * <[4] Y location (signed 32-bit)> + * <[4] Z location (signed 32-bit)> + * <[8] local clock at this member> + * <[8] load average> + * <[8] number of peers> + * <[8] flags (currently unused, must be zero)> + * <[1] number of preferred ZeroTier endpoints> + * <[...] InetAddress(es) of preferred ZeroTier endpoint(s)> + * + * Cluster members constantly broadcast an alive heartbeat and will only + * receive peer redirects if they've done so within the timeout. + */ + CLUSTER_MESSAGE_ALIVE = 1, + + /** + * Cluster member has this peer: + * <[...] serialized identity of peer> + * + * This is typically sent in response to WANT_PEER but can also be pushed + * to prepopulate if this makes sense. + */ + CLUSTER_MESSAGE_HAVE_PEER = 2, + + /** + * Cluster member wants this peer: + * <[5] ZeroTier address of peer> + * + * Members that have a direct link to this peer will respond with + * HAVE_PEER. + */ + CLUSTER_MESSAGE_WANT_PEER = 3, + + /** + * A remote packet that we should also possibly respond to: + * <[2] 16-bit length of remote packet> + * <[...] remote packet payload> + * + * Cluster members may relay requests by relaying the request packet. + * These may include requests such as WHOIS and MULTICAST_GATHER. The + * packet must be already decrypted, decompressed, and authenticated. + * + * This can only be used for small request packets as per the cluster + * message size limit, but since these are the only ones in question + * this is fine. + * + * If a response is generated it is sent via PROXY_SEND. + */ + CLUSTER_MESSAGE_REMOTE_PACKET = 4, + + /** + * Request that VERB_RENDEZVOUS be sent to a peer that we have: + * <[5] ZeroTier address of peer on recipient's side> + * <[5] ZeroTier address of peer on sender's side> + * <[1] 8-bit number of sender's peer's active path addresses> + * <[...] series of serialized InetAddresses of sender's peer's paths> + * + * This requests that we perform NAT-t introduction between a peer that + * we have and one on the sender's side. The sender furnishes contact + * info for its peer, and we send VERB_RENDEZVOUS to both sides: to ours + * directly and with PROXY_SEND to theirs. + */ + CLUSTER_MESSAGE_PROXY_UNITE = 5, + + /** + * Request that a cluster member send a packet to a locally-known peer: + * <[5] ZeroTier address of recipient> + * <[1] packet verb> + * <[2] length of packet payload> + * <[...] packet payload> + * + * This differs from RELAY in that it requests the receiving cluster + * member to actually compose a ZeroTier Packet from itself to the + * provided recipient. RELAY simply says "please forward this blob." + * RELAY is used to implement peer-to-peer relaying with RENDEZVOUS, + * while PROXY_SEND is used to implement proxy sending (which right + * now is only used to send RENDEZVOUS). + */ + CLUSTER_MESSAGE_PROXY_SEND = 6, + + /** + * Replicate a network config for a network we belong to: + * <[...] network config chunk> + * + * This is used by clusters to avoid every member having to query + * for the same netconf for networks all members belong to. + * + * The first field of a network config chunk is the network ID, + * so this can be checked to look up the network on receipt. + */ + CLUSTER_MESSAGE_NETWORK_CONFIG = 7 + }; + + /** + * Construct a new cluster + */ + Cluster( + const RuntimeEnvironment *renv, + uint16_t id, + const std::vector &zeroTierPhysicalEndpoints, + int32_t x, + int32_t y, + int32_t z, + void (*sendFunction)(void *,unsigned int,const void *,unsigned int), + void *sendFunctionArg, + int (*addressToLocationFunction)(void *,const struct sockaddr_storage *,int *,int *,int *), + void *addressToLocationFunctionArg); + + ~Cluster(); + + /** + * @return This cluster member's ID + */ + inline uint16_t id() const throw() { return _id; } + + /** + * Handle an incoming intra-cluster message + * + * @param data Message data + * @param len Message length (max: ZT_CLUSTER_MAX_MESSAGE_LENGTH) + */ + void handleIncomingStateMessage(const void *msg,unsigned int len); + + /** + * Broadcast that we have a given peer + * + * This should be done when new peers are first contacted. + * + * @param id Identity of peer + */ + void broadcastHavePeer(const Identity &id); + + /** + * Broadcast a network config chunk to other members of cluster + * + * @param chunk Chunk data + * @param len Length of chunk + */ + void broadcastNetworkConfigChunk(const void *chunk,unsigned int len); + + /** + * If the cluster has this peer, prepare the packet to send via cluster + * + * Note that outp is only armored (or modified at all) if the return value is a member ID. + * + * @param toPeerAddress Value of outp.destination(), simply to save additional lookup + * @param ts Result: set to time of last HAVE_PEER from the cluster + * @param peerSecret Result: Buffer to fill with peer secret on valid return value, must be at least ZT_PEER_SECRET_KEY_LENGTH bytes + * @return -1 if cluster does not know this peer, or a member ID to pass to sendViaCluster() + */ + int checkSendViaCluster(const Address &toPeerAddress,uint64_t &mostRecentTs,void *peerSecret); + + /** + * Send data via cluster front plane (packet head or fragment) + * + * @param haveMemberId Member ID that has this peer as returned by prepSendviaCluster() + * @param toPeerAddress Destination peer address + * @param data Packet or packet fragment data + * @param len Length of packet or fragment + * @return True if packet was sent (and outp was modified via armoring) + */ + bool sendViaCluster(int haveMemberId,const Address &toPeerAddress,const void *data,unsigned int len); + + /** + * Relay a packet via the cluster + * + * This is used in the outgoing packet and relaying logic in Switch to + * relay packets to other cluster members. It isn't PROXY_SEND-- that is + * used internally in Cluster to send responses to peer queries. + * + * @param fromPeerAddress Source peer address (if known, should be NULL for fragments) + * @param toPeerAddress Destination peer address + * @param data Packet or packet fragment data + * @param len Length of packet or fragment + * @param unite If true, also request proxy unite across cluster + */ + void relayViaCluster(const Address &fromPeerAddress,const Address &toPeerAddress,const void *data,unsigned int len,bool unite); + + /** + * Send a distributed query to other cluster members + * + * Some queries such as WHOIS or MULTICAST_GATHER need a response from other + * cluster members. Replies (if any) will be sent back to the peer via + * PROXY_SEND across the cluster. + * + * @param pkt Packet to distribute + */ + void sendDistributedQuery(const Packet &pkt); + + /** + * Call every ~ZT_CLUSTER_PERIODIC_TASK_PERIOD milliseconds. + */ + void doPeriodicTasks(); + + /** + * Add a member ID to this cluster + * + * @param memberId Member ID + */ + void addMember(uint16_t memberId); + + /** + * Remove a member ID from this cluster + * + * @param memberId Member ID to remove + */ + void removeMember(uint16_t memberId); + + /** + * Find a better cluster endpoint for this peer (if any) + * + * @param redirectTo InetAddress to be set to a better endpoint (if there is one) + * @param peerAddress Address of peer to (possibly) redirect + * @param peerPhysicalAddress Physical address of peer's current best path (where packet was most recently received or getBestPath()->address()) + * @param offload Always redirect if possible -- can be used to offload peers during shutdown + * @return True if redirectTo was set to a new address, false if redirectTo was not modified + */ + bool findBetterEndpoint(InetAddress &redirectTo,const Address &peerAddress,const InetAddress &peerPhysicalAddress,bool offload); + + /** + * @param ip Address to check + * @return True if this is a cluster frontplane address (excluding our addresses) + */ + bool isClusterPeerFrontplane(const InetAddress &ip) const; + + /** + * Fill out ZT_ClusterStatus structure (from core API) + * + * @param status Reference to structure to hold result (anything there is replaced) + */ + void status(ZT_ClusterStatus &status) const; + +private: + void _send(uint16_t memberId,StateMessageType type,const void *msg,unsigned int len); + void _flush(uint16_t memberId); + + void _doREMOTE_WHOIS(uint64_t fromMemberId,const Packet &remotep); + void _doREMOTE_MULTICAST_GATHER(uint64_t fromMemberId,const Packet &remotep); + + // These are initialized in the constructor and remain immutable ------------ + uint16_t _masterSecret[ZT_SHA512_DIGEST_LEN / sizeof(uint16_t)]; + unsigned char _key[ZT_PEER_SECRET_KEY_LENGTH]; + const RuntimeEnvironment *RR; + _ClusterSendQueue *const _sendQueue; + void (*_sendFunction)(void *,unsigned int,const void *,unsigned int); + void *_sendFunctionArg; + int (*_addressToLocationFunction)(void *,const struct sockaddr_storage *,int *,int *,int *); + void *_addressToLocationFunctionArg; + const int32_t _x; + const int32_t _y; + const int32_t _z; + const uint16_t _id; + const std::vector _zeroTierPhysicalEndpoints; + // end immutable fields ----------------------------------------------------- + + struct _Member + { + unsigned char key[ZT_PEER_SECRET_KEY_LENGTH]; + + uint64_t lastReceivedAliveAnnouncement; + uint64_t lastAnnouncedAliveTo; + + uint64_t load; + uint64_t peers; + int32_t x,y,z; + + std::vector zeroTierPhysicalEndpoints; + + Buffer q; + + Mutex lock; + + inline void clear() + { + lastReceivedAliveAnnouncement = 0; + lastAnnouncedAliveTo = 0; + load = 0; + peers = 0; + x = 0; + y = 0; + z = 0; + zeroTierPhysicalEndpoints.clear(); + q.clear(); + } + + _Member() { this->clear(); } + ~_Member() { Utils::burn(key,sizeof(key)); } + }; + _Member *const _members; + + std::vector _memberIds; + Mutex _memberIds_m; + + struct _RemotePeer + { + _RemotePeer() : lastHavePeerReceived(0),lastSentWantPeer(0) {} + ~_RemotePeer() { Utils::burn(key,ZT_PEER_SECRET_KEY_LENGTH); } + uint64_t lastHavePeerReceived; + uint64_t lastSentWantPeer; + uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]; // secret key from identity agreement + }; + std::map< std::pair,_RemotePeer > _remotePeers; // we need ordered behavior and lower_bound here + Mutex _remotePeers_m; + + uint64_t _lastFlushed; + uint64_t _lastCleanedRemotePeers; + uint64_t _lastCleanedQueue; +}; + +} // namespace ZeroTier + +#endif // ZT_ENABLE_CLUSTER + +#endif diff --git a/node/Constants.hpp b/node/Constants.hpp new file mode 100644 index 0000000..93184ef --- /dev/null +++ b/node/Constants.hpp @@ -0,0 +1,457 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_CONSTANTS_HPP +#define ZT_CONSTANTS_HPP + +#include "../include/ZeroTierOne.h" + +// +// This include file also auto-detects and canonicalizes some environment +// information defines: +// +// __LINUX__ +// __APPLE__ +// __BSD__ (OSX also defines this) +// __UNIX_LIKE__ (Linux, BSD, etc.) +// __WINDOWS__ +// +// Also makes sure __BYTE_ORDER is defined reasonably. +// + +// Hack: make sure __GCC__ is defined on old GCC compilers +#ifndef __GCC__ +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1) || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2) || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) +#define __GCC__ +#endif +#endif + +#if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) +#ifndef __LINUX__ +#define __LINUX__ +#endif +#ifndef __UNIX_LIKE__ +#define __UNIX_LIKE__ +#endif +#include +#endif + +#ifdef __APPLE__ +#include +#ifndef __UNIX_LIKE__ +#define __UNIX_LIKE__ +#endif +#ifndef __BSD__ +#define __BSD__ +#endif +#include +#endif + +// Defined this macro to disable "type punning" on a number of targets that +// have issues with unaligned memory access. +#if defined(__arm__) || defined(__ARMEL__) || (defined(__APPLE__) && ( (defined(TARGET_OS_IPHONE) && (TARGET_OS_IPHONE != 0)) || (defined(TARGET_OS_WATCH) && (TARGET_OS_WATCH != 0)) || (defined(TARGET_IPHONE_SIMULATOR) && (TARGET_IPHONE_SIMULATOR != 0)) ) ) +#ifndef ZT_NO_TYPE_PUNNING +#define ZT_NO_TYPE_PUNNING +#endif +#endif + +#if defined(__FreeBSD__) || defined(__OpenBSD__) +#ifndef __UNIX_LIKE__ +#define __UNIX_LIKE__ +#endif +#ifndef __BSD__ +#define __BSD__ +#endif +#include +#ifndef __BYTE_ORDER +#define __BYTE_ORDER _BYTE_ORDER +#define __LITTLE_ENDIAN _LITTLE_ENDIAN +#define __BIG_ENDIAN _BIG_ENDIAN +#endif +#endif + +#if defined(_WIN32) || defined(_WIN64) +#ifndef __WINDOWS__ +#define __WINDOWS__ +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#pragma warning(disable : 4290) +#pragma warning(disable : 4996) +#pragma warning(disable : 4101) +#undef __UNIX_LIKE__ +#undef __BSD__ +#define ZT_PATH_SEPARATOR '\\' +#define ZT_PATH_SEPARATOR_S "\\" +#define ZT_EOL_S "\r\n" +#include +#include +#endif + +// Assume little endian if not defined +#if (defined(__APPLE__) || defined(__WINDOWS__)) && (!defined(__BYTE_ORDER)) +#undef __BYTE_ORDER +#undef __LITTLE_ENDIAN +#undef __BIG_ENDIAN +#define __BIG_ENDIAN 4321 +#define __LITTLE_ENDIAN 1234 +#define __BYTE_ORDER 1234 +#endif + +#ifdef __UNIX_LIKE__ +#define ZT_PATH_SEPARATOR '/' +#define ZT_PATH_SEPARATOR_S "/" +#define ZT_EOL_S "\n" +#endif + +#ifndef __BYTE_ORDER +#include +#endif + +/** + * Length of a ZeroTier address in bytes + */ +#define ZT_ADDRESS_LENGTH 5 + +/** + * Length of a hexadecimal ZeroTier address + */ +#define ZT_ADDRESS_LENGTH_HEX 10 + +/** + * Addresses beginning with this byte are reserved for the joy of in-band signaling + */ +#define ZT_ADDRESS_RESERVED_PREFIX 0xff + +/** + * Default payload MTU for UDP packets + * + * In the future we might support UDP path MTU discovery, but for now we + * set a maximum that is equal to 1500 minus 8 (for PPPoE overhead, common + * in some markets) minus 48 (IPv6 UDP overhead). + */ +#define ZT_UDP_DEFAULT_PAYLOAD_MTU 1444 + +/** + * Default MTU used for Ethernet tap device + */ +#define ZT_IF_MTU ZT_MAX_MTU + +/** + * Maximum number of packet fragments we'll support + * + * The actual spec allows 16, but this is the most we'll support right + * now. Packets with more than this many fragments are dropped. + */ +#define ZT_MAX_PACKET_FRAGMENTS 4 + +/** + * Size of RX queue + * + * This is about 2mb, and can be decreased for small devices. A queue smaller + * than about 4 is probably going to cause a lot of lost packets. + */ +#define ZT_RX_QUEUE_SIZE 64 + +/** + * RX queue entries older than this do not "exist" + */ +#define ZT_RX_QUEUE_EXPIRE 4000 + +/** + * Length of secret key in bytes -- 256-bit -- do not change + */ +#define ZT_PEER_SECRET_KEY_LENGTH 32 + +/** + * Minimum delay between timer task checks to prevent thrashing + */ +#define ZT_CORE_TIMER_TASK_GRANULARITY 500 + +/** + * How often Topology::clean() and Network::clean() and similar are called, in ms + */ +#define ZT_HOUSEKEEPING_PERIOD 120000 + +/** + * How long to remember peer records in RAM if they haven't been used + */ +#define ZT_PEER_IN_MEMORY_EXPIRATION 600000 + +/** + * Delay between WHOIS retries in ms + */ +#define ZT_WHOIS_RETRY_DELAY 1000 + +/** + * Maximum identity WHOIS retries (each attempt tries consulting a different peer) + */ +#define ZT_MAX_WHOIS_RETRIES 4 + +/** + * Transmit queue entry timeout + */ +#define ZT_TRANSMIT_QUEUE_TIMEOUT (ZT_WHOIS_RETRY_DELAY * (ZT_MAX_WHOIS_RETRIES + 1)) + +/** + * Receive queue entry timeout + */ +#define ZT_RECEIVE_QUEUE_TIMEOUT (ZT_WHOIS_RETRY_DELAY * (ZT_MAX_WHOIS_RETRIES + 1)) + +/** + * Maximum latency to allow for OK(HELLO) before packet is discarded + */ +#define ZT_HELLO_MAX_ALLOWABLE_LATENCY 60000 + +/** + * Maximum number of ZT hops allowed (this is not IP hops/TTL) + * + * The protocol allows up to 7, but we limit it to something smaller. + */ +#define ZT_RELAY_MAX_HOPS 3 + +/** + * Maximum number of upstreams to use (far more than we should ever need) + */ +#define ZT_MAX_UPSTREAMS 64 + +/** + * Expire time for multicast 'likes' and indirect multicast memberships in ms + */ +#define ZT_MULTICAST_LIKE_EXPIRE 600000 + +/** + * Period for multicast LIKE announcements + */ +#define ZT_MULTICAST_ANNOUNCE_PERIOD 120000 + +/** + * Delay between explicit MULTICAST_GATHER requests for a given multicast channel + */ +#define ZT_MULTICAST_EXPLICIT_GATHER_DELAY (ZT_MULTICAST_LIKE_EXPIRE / 10) + +/** + * Expiration for credentials presented for MULTICAST_LIKE or MULTICAST_GATHER (for non-network-members) + */ +#define ZT_MULTICAST_CREDENTIAL_EXPIRATON ZT_MULTICAST_LIKE_EXPIRE + +/** + * Timeout for outgoing multicasts + * + * This is how long we wait for explicit or implicit gather results. + */ +#define ZT_MULTICAST_TRANSMIT_TIMEOUT 5000 + +/** + * Delay between checks of peer pings, etc., and also related housekeeping tasks + */ +#define ZT_PING_CHECK_INVERVAL 5000 + +/** + * How frequently to send heartbeats over in-use paths + */ +#define ZT_PATH_HEARTBEAT_PERIOD 14000 + +/** + * Paths are considered inactive if they have not received traffic in this long + */ +#define ZT_PATH_ALIVE_TIMEOUT 45000 + +/** + * Minimum time between attempts to check dead paths to see if they can be re-awakened + */ +#define ZT_PATH_MIN_REACTIVATE_INTERVAL 2500 + +/** + * Do not accept HELLOs over a given path more often than this + */ +#define ZT_PATH_HELLO_RATE_LIMIT 1000 + +/** + * Delay between full-fledge pings of directly connected peers + */ +#define ZT_PEER_PING_PERIOD 60000 + +/** + * Paths are considered expired if they have not sent us a real packet in this long + */ +#define ZT_PEER_PATH_EXPIRATION ((ZT_PEER_PING_PERIOD * 4) + 3000) + +/** + * Send a full HELLO every this often (ms) + */ +#define ZT_PEER_SEND_FULL_HELLO_EVERY (ZT_PEER_PING_PERIOD * 2) + +/** + * How often to retry expired paths that we're still remembering + */ +#define ZT_PEER_EXPIRED_PATH_TRIAL_PERIOD (ZT_PEER_PING_PERIOD * 10) + +/** + * Timeout for overall peer activity (measured from last receive) + */ +#define ZT_PEER_ACTIVITY_TIMEOUT 500000 + +/** + * General rate limit timeout for multiple packet types (HELLO, etc.) + */ +#define ZT_PEER_GENERAL_INBOUND_RATE_LIMIT 500 + +/** + * General limit for max RTT for requests over the network + */ +#define ZT_GENERAL_RTT_LIMIT 5000 + +/** + * Delay between requests for updated network autoconf information + * + * Don't lengthen this as it affects things like QoS / uptime monitoring + * via ZeroTier Central. This is the heartbeat, basically. + */ +#define ZT_NETWORK_AUTOCONF_DELAY 60000 + +/** + * Minimum interval between attempts by relays to unite peers + * + * When a relay gets a packet destined for another peer, it sends both peers + * a RENDEZVOUS message no more than this often. This instructs the peers + * to attempt NAT-t and gives each the other's corresponding IP:port pair. + */ +#define ZT_MIN_UNITE_INTERVAL 30000 + +/** + * How often should peers try memorized or statically defined paths? + */ +#define ZT_TRY_MEMORIZED_PATH_INTERVAL 30000 + +/** + * Sanity limit on maximum bridge routes + * + * If the number of bridge routes exceeds this, we cull routes from the + * bridges with the most MACs behind them until it doesn't. This is a + * sanity limit to prevent memory-filling DOS attacks, nothing more. No + * physical LAN has anywhere even close to this many nodes. Note that this + * does not limit the size of ZT virtual LANs, only bridge routing. + */ +#define ZT_MAX_BRIDGE_ROUTES 67108864 + +/** + * If there is no known route, spam to up to this many active bridges + */ +#define ZT_MAX_BRIDGE_SPAM 32 + +/** + * Interval between direct path pushes in milliseconds + */ +#define ZT_DIRECT_PATH_PUSH_INTERVAL 120000 + +/** + * Time horizon for push direct paths cutoff + */ +#define ZT_PUSH_DIRECT_PATHS_CUTOFF_TIME 60000 + +/** + * Maximum number of direct path pushes within cutoff time + * + * This limits response to PUSH_DIRECT_PATHS to CUTOFF_LIMIT responses + * per CUTOFF_TIME milliseconds per peer to prevent this from being + * useful for DOS amplification attacks. + */ +#define ZT_PUSH_DIRECT_PATHS_CUTOFF_LIMIT 5 + +/** + * Maximum number of paths per IP scope (e.g. global, link-local) and family (e.g. v4/v6) + */ +#define ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY 4 + +/** + * Time horizon for VERB_NETWORK_CREDENTIALS cutoff + */ +#define ZT_PEER_CREDENTIALS_CUTOFF_TIME 60000 + +/** + * Maximum number of VERB_NETWORK_CREDENTIALS within cutoff time + */ +#define ZT_PEER_CREDEITIALS_CUTOFF_LIMIT 15 + +/** + * WHOIS rate limit (we allow these to be pretty fast) + */ +#define ZT_PEER_WHOIS_RATE_LIMIT 100 + +/** + * General rate limit for other kinds of rate-limited packets (HELLO, credential request, etc.) both inbound and outbound + */ +#define ZT_PEER_GENERAL_RATE_LIMIT 1000 + +/** + * Don't do expensive identity validation more often than this + * + * IPv4 and IPv6 address prefixes are hashed down to 14-bit (0-16383) integers + * using the first 24 bits for IPv4 or the first 48 bits for IPv6. These are + * then rate limited to one identity validation per this often milliseconds. + */ +#if (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__) || defined(_M_X64) || defined(_M_AMD64)) +// AMD64 machines can do anywhere from one every 50ms to one every 10ms. This provides plenty of margin. +#define ZT_IDENTITY_VALIDATION_SOURCE_RATE_LIMIT 2000 +#else +#if (defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(_X86_) || defined(__I86__)) +// 32-bit Intel machines usually average about one every 100ms +#define ZT_IDENTITY_VALIDATION_SOURCE_RATE_LIMIT 5000 +#else +// This provides a safe margin for ARM, MIPS, etc. that usually average one every 250-400ms +#define ZT_IDENTITY_VALIDATION_SOURCE_RATE_LIMIT 10000 +#endif +#endif + +/** + * How long is a path or peer considered to have a trust relationship with us (for e.g. relay policy) since last trusted established packet? + */ +#define ZT_TRUST_EXPIRATION 600000 + +/** + * Enable support for older network configurations from older (pre-1.1.6) controllers + */ +#define ZT_SUPPORT_OLD_STYLE_NETCONF 1 + +/** + * Desired buffer size for UDP sockets (used in service and osdep but defined here) + */ +#if (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__)) +#define ZT_UDP_DESIRED_BUF_SIZE 1048576 +#else +#define ZT_UDP_DESIRED_BUF_SIZE 131072 +#endif + +/** + * Desired / recommended min stack size for threads (used on some platforms to reset thread stack size) + */ +#define ZT_THREAD_MIN_STACK_SIZE 1048576 + +/* Ethernet frame types that might be relevant to us */ +#define ZT_ETHERTYPE_IPV4 0x0800 +#define ZT_ETHERTYPE_ARP 0x0806 +#define ZT_ETHERTYPE_RARP 0x8035 +#define ZT_ETHERTYPE_ATALK 0x809b +#define ZT_ETHERTYPE_AARP 0x80f3 +#define ZT_ETHERTYPE_IPX_A 0x8137 +#define ZT_ETHERTYPE_IPX_B 0x8138 +#define ZT_ETHERTYPE_IPV6 0x86dd + +#endif diff --git a/node/Credential.hpp b/node/Credential.hpp new file mode 100644 index 0000000..0ae2a0a --- /dev/null +++ b/node/Credential.hpp @@ -0,0 +1,58 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_CREDENTIAL_HPP +#define ZT_CREDENTIAL_HPP + +#include +#include +#include + +#include +#include +#include +#include + +#include "Constants.hpp" + +namespace ZeroTier { + +/** + * Base class for credentials + */ +class Credential +{ +public: + /** + * Do not change type code IDs -- these are used in Revocation objects and elsewhere + */ + enum Type + { + CREDENTIAL_TYPE_NULL = 0, + CREDENTIAL_TYPE_COM = 1, // CertificateOfMembership + CREDENTIAL_TYPE_CAPABILITY = 2, + CREDENTIAL_TYPE_TAG = 3, + CREDENTIAL_TYPE_COO = 4, // CertificateOfOwnership + CREDENTIAL_TYPE_COR = 5, // CertificateOfRepresentation + CREDENTIAL_TYPE_REVOCATION = 6 + }; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Dictionary.hpp b/node/Dictionary.hpp new file mode 100644 index 0000000..0db13b6 --- /dev/null +++ b/node/Dictionary.hpp @@ -0,0 +1,433 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_DICTIONARY_HPP +#define ZT_DICTIONARY_HPP + +#include "Constants.hpp" +#include "Utils.hpp" +#include "Buffer.hpp" +#include "Address.hpp" + +#include + +namespace ZeroTier { + +/** + * A small (in code and data) packed key=value store + * + * This stores data in the form of a compact blob that is sort of human + * readable (depending on whether you put binary data in it) and is backward + * compatible with older versions. Binary data is escaped such that the + * serialized form of a Dictionary is always a valid null-terminated C string. + * + * Keys are restricted: no binary data, no CR/LF, and no equals (=). If a key + * contains these characters it may not be retrievable. This is not checked. + * + * Lookup is via linear search and will be slow with a lot of keys. It's + * designed for small things. + * + * There is code to test and fuzz this in selftest.cpp. Fuzzing a blob of + * pointer tricks like this is important after any modifications. + * + * This is used for network configurations and for saving some things on disk + * in the ZeroTier One service code. + * + * @tparam C Dictionary max capacity in bytes + */ +template +class Dictionary +{ +public: + Dictionary() + { + _d[0] = (char)0; + } + + Dictionary(const char *s) + { + if (s) { + Utils::scopy(_d,sizeof(_d),s); + } else { + _d[0] = (char)0; + } + } + + Dictionary(const char *s,unsigned int len) + { + if (s) { + if (len > (C-1)) + len = C-1; + memcpy(_d,s,len); + _d[len] = (char)0; + } else { + _d[0] = (char)0; + } + } + + Dictionary(const Dictionary &d) + { + Utils::scopy(_d,sizeof(_d),d._d); + } + + inline Dictionary &operator=(const Dictionary &d) + { + Utils::scopy(_d,sizeof(_d),d._d); + return *this; + } + + /** + * Load a dictionary from a C-string + * + * @param s Dictionary in string form + * @return False if 's' was longer than our capacity + */ + inline bool load(const char *s) + { + if (s) { + return Utils::scopy(_d,sizeof(_d),s); + } else { + _d[0] = (char)0; + return true; + } + } + + /** + * Delete all entries + */ + inline void clear() + { + _d[0] = (char)0; + } + + /** + * @return Size of dictionary in bytes not including terminating NULL + */ + inline unsigned int sizeBytes() const + { + for(unsigned int i=0;i + inline bool get(const char *key,Buffer &dest) const + { + const int r = this->get(key,const_cast(reinterpret_cast(dest.data())),BC); + if (r >= 0) { + dest.setSize((unsigned int)r); + return true; + } else { + dest.clear(); + return false; + } + } + + /** + * Get a boolean value + * + * @param key Key to look up + * @param dfl Default value if not found in dictionary + * @return Boolean value of key or 'dfl' if not found + */ + bool getB(const char *key,bool dfl = false) const + { + char tmp[4]; + if (this->get(key,tmp,sizeof(tmp)) >= 0) + return ((*tmp == '1')||(*tmp == 't')||(*tmp == 'T')); + return dfl; + } + + /** + * Get an unsigned int64 stored as hex in the dictionary + * + * @param key Key to look up + * @param dfl Default value or 0 if unspecified + * @return Decoded hex UInt value or 'dfl' if not found + */ + inline uint64_t getUI(const char *key,uint64_t dfl = 0) const + { + char tmp[128]; + if (this->get(key,tmp,sizeof(tmp)) >= 1) + return Utils::hexStrToU64(tmp); + return dfl; + } + + /** + * Add a new key=value pair + * + * If the key is already present this will append another, but the first + * will always be returned by get(). This is not checked. If you want to + * ensure a key is not present use erase() first. + * + * Use the vlen parameter to add binary values. Nulls will be escaped. + * + * @param key Key -- nulls, CR/LF, and equals (=) are illegal characters + * @param value Value to set + * @param vlen Length of value in bytes or -1 to treat value[] as a C-string and look for terminating 0 + * @return True if there was enough room to add this key=value pair + */ + inline bool add(const char *key,const char *value,int vlen = -1) + { + for(unsigned int i=0;i 0) { + _d[j++] = (char)10; + if (j == C) { + _d[i] = (char)0; + return false; + } + } + + const char *p = key; + while (*p) { + _d[j++] = *(p++); + if (j == C) { + _d[i] = (char)0; + return false; + } + } + + _d[j++] = '='; + if (j == C) { + _d[i] = (char)0; + return false; + } + + p = value; + int k = 0; + while ( ((vlen < 0)&&(*p)) || (k < vlen) ) { + switch(*p) { + case 0: + case 13: + case 10: + case '\\': + case '=': + _d[j++] = '\\'; + if (j == C) { + _d[i] = (char)0; + return false; + } + switch(*p) { + case 0: _d[j++] = '0'; break; + case 13: _d[j++] = 'r'; break; + case 10: _d[j++] = 'n'; break; + case '\\': _d[j++] = '\\'; break; + case '=': _d[j++] = 'e'; break; + } + if (j == C) { + _d[i] = (char)0; + return false; + } + break; + default: + _d[j++] = *p; + if (j == C) { + _d[i] = (char)0; + return false; + } + break; + } + ++p; + ++k; + } + + _d[j] = (char)0; + + return true; + } + } + return false; + } + + /** + * Add a boolean as a '1' or a '0' + */ + inline bool add(const char *key,bool value) + { + return this->add(key,(value) ? "1" : "0",1); + } + + /** + * Add a 64-bit integer (unsigned) as a hex value + */ + inline bool add(const char *key,uint64_t value) + { + char tmp[32]; + Utils::snprintf(tmp,sizeof(tmp),"%llx",(unsigned long long)value); + return this->add(key,tmp,-1); + } + + /** + * Add a 64-bit integer (unsigned) as a hex value + */ + inline bool add(const char *key,const Address &a) + { + char tmp[32]; + Utils::snprintf(tmp,sizeof(tmp),"%.10llx",(unsigned long long)a.toInt()); + return this->add(key,tmp,-1); + } + + /** + * Add a binary buffer's contents as a value + * + * @tparam BC Buffer capacity (usually inferred) + */ + template + inline bool add(const char *key,const Buffer &value) + { + return this->add(key,(const char *)value.data(),(int)value.size()); + } + + /** + * @param key Key to check + * @return True if key is present + */ + inline bool contains(const char *key) const + { + char tmp[2]; + return (this->get(key,tmp,2) >= 0); + } + + /** + * @return Value of C template parameter + */ + inline unsigned int capacity() const { return C; } + + inline const char *data() const { return _d; } + inline char *unsafeData() { return _d; } + +private: + char _d[C]; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Hashtable.hpp b/node/Hashtable.hpp new file mode 100644 index 0000000..66f2990 --- /dev/null +++ b/node/Hashtable.hpp @@ -0,0 +1,415 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_HASHTABLE_HPP +#define ZT_HASHTABLE_HPP + +#include +#include +#include + +#include +#include +#include +#include + +namespace ZeroTier { + +/** + * A minimal hash table implementation for the ZeroTier core + * + * This is not a drop-in replacement for STL containers, and has several + * limitations. Keys can be uint64_t or an object, and if the latter they + * must implement a method called hashCode() that returns an unsigned long + * value that is evenly distributed. + */ +template +class Hashtable +{ +private: + struct _Bucket + { + _Bucket(const K &k,const V &v) : k(k),v(v) {} + _Bucket(const K &k) : k(k),v() {} + _Bucket(const _Bucket &b) : k(b.k),v(b.v) {} + inline _Bucket &operator=(const _Bucket &b) { k = b.k; v = b.v; return *this; } + K k; + V v; + _Bucket *next; // must be set manually for each _Bucket + }; + +public: + /** + * A simple forward iterator (different from STL) + * + * It's safe to erase the last key, but not others. Don't use set() since that + * may rehash and invalidate the iterator. Note the erasing the key will destroy + * the targets of the pointers returned by next(). + */ + class Iterator + { + public: + /** + * @param ht Hash table to iterate over + */ + Iterator(Hashtable &ht) : + _idx(0), + _ht(&ht), + _b(ht._t[0]) + { + } + + /** + * @param kptr Pointer to set to point to next key + * @param vptr Pointer to set to point to next value + * @return True if kptr and vptr are set, false if no more entries + */ + inline bool next(K *&kptr,V *&vptr) + { + for(;;) { + if (_b) { + kptr = &(_b->k); + vptr = &(_b->v); + _b = _b->next; + return true; + } + ++_idx; + if (_idx >= _ht->_bc) + return false; + _b = _ht->_t[_idx]; + } + } + + private: + unsigned long _idx; + Hashtable *_ht; + _Bucket *_b; + }; + friend class Hashtable::Iterator; + + /** + * @param bc Initial capacity in buckets (default: 64, must be nonzero) + */ + Hashtable(unsigned long bc = 64) : + _t(reinterpret_cast<_Bucket **>(::malloc(sizeof(_Bucket *) * bc))), + _bc(bc), + _s(0) + { + if (!_t) + throw std::bad_alloc(); + for(unsigned long i=0;i &ht) : + _t(reinterpret_cast<_Bucket **>(::malloc(sizeof(_Bucket *) * ht._bc))), + _bc(ht._bc), + _s(ht._s) + { + if (!_t) + throw std::bad_alloc(); + for(unsigned long i=0;i<_bc;++i) + _t[i] = (_Bucket *)0; + for(unsigned long i=0;i<_bc;++i) { + const _Bucket *b = ht._t[i]; + while (b) { + _Bucket *nb = new _Bucket(*b); + nb->next = _t[i]; + _t[i] = nb; + b = b->next; + } + } + } + + ~Hashtable() + { + this->clear(); + ::free(_t); + } + + inline Hashtable &operator=(const Hashtable &ht) + { + this->clear(); + if (ht._s) { + for(unsigned long i=0;iset(b->k,b->v); + b = b->next; + } + } + } + return *this; + } + + /** + * Erase all entries + */ + inline void clear() + { + if (_s) { + for(unsigned long i=0;i<_bc;++i) { + _Bucket *b = _t[i]; + while (b) { + _Bucket *const nb = b->next; + delete b; + b = nb; + } + _t[i] = (_Bucket *)0; + } + _s = 0; + } + } + + /** + * @return Vector of all keys + */ + inline typename std::vector keys() const + { + typename std::vector k; + if (_s) { + k.reserve(_s); + for(unsigned long i=0;i<_bc;++i) { + _Bucket *b = _t[i]; + while (b) { + k.push_back(b->k); + b = b->next; + } + } + } + return k; + } + + /** + * Append all keys (in unspecified order) to the supplied vector or list + * + * @param v Vector, list, or other compliant container + * @tparam Type of V (generally inferred) + */ + template + inline void appendKeys(C &v) const + { + if (_s) { + for(unsigned long i=0;i<_bc;++i) { + _Bucket *b = _t[i]; + while (b) { + v.push_back(b->k); + b = b->next; + } + } + } + } + + /** + * @return Vector of all entries (pairs of K,V) + */ + inline typename std::vector< std::pair > entries() const + { + typename std::vector< std::pair > k; + if (_s) { + k.reserve(_s); + for(unsigned long i=0;i<_bc;++i) { + _Bucket *b = _t[i]; + while (b) { + k.push_back(std::pair(b->k,b->v)); + b = b->next; + } + } + } + return k; + } + + /** + * @param k Key + * @return Pointer to value or NULL if not found + */ + inline V *get(const K &k) + { + _Bucket *b = _t[_hc(k) % _bc]; + while (b) { + if (b->k == k) + return &(b->v); + b = b->next; + } + return (V *)0; + } + inline const V *get(const K &k) const { return const_cast(this)->get(k); } + + /** + * @param k Key to check + * @return True if key is present + */ + inline bool contains(const K &k) const + { + _Bucket *b = _t[_hc(k) % _bc]; + while (b) { + if (b->k == k) + return true; + b = b->next; + } + return false; + } + + /** + * @param k Key + * @return True if value was present + */ + inline bool erase(const K &k) + { + const unsigned long bidx = _hc(k) % _bc; + _Bucket *lastb = (_Bucket *)0; + _Bucket *b = _t[bidx]; + while (b) { + if (b->k == k) { + if (lastb) + lastb->next = b->next; + else _t[bidx] = b->next; + delete b; + --_s; + return true; + } + lastb = b; + b = b->next; + } + return false; + } + + /** + * @param k Key + * @param v Value + * @return Reference to value in table + */ + inline V &set(const K &k,const V &v) + { + const unsigned long h = _hc(k); + unsigned long bidx = h % _bc; + + _Bucket *b = _t[bidx]; + while (b) { + if (b->k == k) { + b->v = v; + return b->v; + } + b = b->next; + } + + if (_s >= _bc) { + _grow(); + bidx = h % _bc; + } + + b = new _Bucket(k,v); + b->next = _t[bidx]; + _t[bidx] = b; + ++_s; + return b->v; + } + + /** + * @param k Key + * @return Value, possibly newly created + */ + inline V &operator[](const K &k) + { + const unsigned long h = _hc(k); + unsigned long bidx = h % _bc; + + _Bucket *b = _t[bidx]; + while (b) { + if (b->k == k) + return b->v; + b = b->next; + } + + if (_s >= _bc) { + _grow(); + bidx = h % _bc; + } + + b = new _Bucket(k); + b->next = _t[bidx]; + _t[bidx] = b; + ++_s; + return b->v; + } + + /** + * @return Number of entries + */ + inline unsigned long size() const throw() { return _s; } + + /** + * @return True if table is empty + */ + inline bool empty() const throw() { return (_s == 0); } + +private: + template + static inline unsigned long _hc(const O &obj) + { + return (unsigned long)obj.hashCode(); + } + static inline unsigned long _hc(const uint64_t i) + { + /* NOTE: this assumes that 'i' is evenly distributed, which is the case for + * packet IDs and network IDs -- the two use cases in ZT for uint64_t keys. + * These values are also greater than 0xffffffff so they'll map onto a full + * bucket count just fine no matter what happens. Normally you'd want to + * hash an integer key index in a hash table. */ + return (unsigned long)i; + } + static inline unsigned long _hc(const uint32_t i) + { + return ((unsigned long)i * (unsigned long)0x9e3779b1); + } + static inline unsigned long _hc(const uint16_t i) + { + return ((unsigned long)i * (unsigned long)0x9e3779b1); + } + + inline void _grow() + { + const unsigned long nc = _bc * 2; + _Bucket **nt = reinterpret_cast<_Bucket **>(::malloc(sizeof(_Bucket *) * nc)); + if (nt) { + for(unsigned long i=0;inext; + const unsigned long nidx = _hc(b->k) % nc; + b->next = nt[nidx]; + nt[nidx] = b; + b = nb; + } + } + ::free(_t); + _t = nt; + _bc = nc; + } + } + + _Bucket **_t; + unsigned long _bc; + unsigned long _s; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Identity.cpp b/node/Identity.cpp new file mode 100644 index 0000000..d1b21e9 --- /dev/null +++ b/node/Identity.cpp @@ -0,0 +1,190 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include + +#include "Constants.hpp" +#include "Identity.hpp" +#include "SHA512.hpp" +#include "Salsa20.hpp" +#include "Utils.hpp" + +// These can't be changed without a new identity type. They define the +// parameters of the hashcash hashing/searching algorithm. + +#define ZT_IDENTITY_GEN_HASHCASH_FIRST_BYTE_LESS_THAN 17 +#define ZT_IDENTITY_GEN_MEMORY 2097152 + +namespace ZeroTier { + +// A memory-hard composition of SHA-512 and Salsa20 for hashcash hashing +static inline void _computeMemoryHardHash(const void *publicKey,unsigned int publicKeyBytes,void *digest,void *genmem) +{ + // Digest publicKey[] to obtain initial digest + SHA512::hash(digest,publicKey,publicKeyBytes); + + // Initialize genmem[] using Salsa20 in a CBC-like configuration since + // ordinary Salsa20 is randomly seekable. This is good for a cipher + // but is not what we want for sequential memory-harndess. + memset(genmem,0,ZT_IDENTITY_GEN_MEMORY); + Salsa20 s20(digest,(char *)digest + 32); + s20.crypt20((char *)genmem,(char *)genmem,64); + for(unsigned long i=64;idata,(unsigned int)_privateKey->size())); + } + + return r; +} + +bool Identity::fromString(const char *str) +{ + if (!str) + return false; + + char *saveptr = (char *)0; + char tmp[1024]; + if (!Utils::scopy(tmp,sizeof(tmp),str)) + return false; + + delete _privateKey; + _privateKey = (C25519::Private *)0; + + int fno = 0; + for(char *f=Utils::stok(tmp,":",&saveptr);(f);f=Utils::stok((char *)0,":",&saveptr)) { + switch(fno++) { + case 0: + _address = Address(Utils::hexStrToU64(f)); + if (_address.isReserved()) + return false; + break; + case 1: + if ((f[0] != '0')||(f[1])) + return false; + break; + case 2: + if (Utils::unhex(f,_publicKey.data,(unsigned int)_publicKey.size()) != _publicKey.size()) + return false; + break; + case 3: + _privateKey = new C25519::Private(); + if (Utils::unhex(f,_privateKey->data,(unsigned int)_privateKey->size()) != _privateKey->size()) + return false; + break; + default: + return false; + } + } + if (fno < 3) + return false; + + return true; +} + +} // namespace ZeroTier diff --git a/node/Identity.hpp b/node/Identity.hpp new file mode 100644 index 0000000..e452273 --- /dev/null +++ b/node/Identity.hpp @@ -0,0 +1,323 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_IDENTITY_HPP +#define ZT_IDENTITY_HPP + +#include +#include +#include + +#include "Constants.hpp" +#include "Array.hpp" +#include "Utils.hpp" +#include "Address.hpp" +#include "C25519.hpp" +#include "Buffer.hpp" +#include "SHA512.hpp" + +namespace ZeroTier { + +/** + * A ZeroTier identity + * + * An identity consists of a public key, a 40-bit ZeroTier address computed + * from that key in a collision-resistant fashion, and a self-signature. + * + * The address derivation algorithm makes it computationally very expensive to + * search for a different public key that duplicates an existing address. (See + * code for deriveAddress() for this algorithm.) + */ +class Identity +{ +public: + Identity() : + _privateKey((C25519::Private *)0) + { + } + + Identity(const Identity &id) : + _address(id._address), + _publicKey(id._publicKey), + _privateKey((id._privateKey) ? new C25519::Private(*(id._privateKey)) : (C25519::Private *)0) + { + } + + Identity(const char *str) + throw(std::invalid_argument) : + _privateKey((C25519::Private *)0) + { + if (!fromString(str)) + throw std::invalid_argument(std::string("invalid string-serialized identity: ") + str); + } + + Identity(const std::string &str) + throw(std::invalid_argument) : + _privateKey((C25519::Private *)0) + { + if (!fromString(str)) + throw std::invalid_argument(std::string("invalid string-serialized identity: ") + str); + } + + template + Identity(const Buffer &b,unsigned int startAt = 0) : + _privateKey((C25519::Private *)0) + { + deserialize(b,startAt); + } + + ~Identity() + { + delete _privateKey; + } + + inline Identity &operator=(const Identity &id) + { + _address = id._address; + _publicKey = id._publicKey; + if (id._privateKey) { + if (!_privateKey) + _privateKey = new C25519::Private(); + *_privateKey = *(id._privateKey); + } else { + delete _privateKey; + _privateKey = (C25519::Private *)0; + } + return *this; + } + + /** + * Generate a new identity (address, key pair) + * + * This is a time consuming operation. + */ + void generate(); + + /** + * Check the validity of this identity's pairing of key to address + * + * @return True if validation check passes + */ + bool locallyValidate() const; + + /** + * @return True if this identity contains a private key + */ + inline bool hasPrivate() const throw() { return (_privateKey != (C25519::Private *)0); } + + /** + * Compute the SHA512 hash of our private key (if we have one) + * + * @param sha Buffer to receive SHA512 (MUST be ZT_SHA512_DIGEST_LEN (64) bytes in length) + * @return True on success, false if no private key + */ + inline bool sha512PrivateKey(void *sha) const + { + if (_privateKey) { + SHA512::hash(sha,_privateKey->data,ZT_C25519_PRIVATE_KEY_LEN); + return true; + } + return false; + } + + /** + * Sign a message with this identity (private key required) + * + * @param data Data to sign + * @param len Length of data + */ + inline C25519::Signature sign(const void *data,unsigned int len) const + throw(std::runtime_error) + { + if (_privateKey) + return C25519::sign(*_privateKey,_publicKey,data,len); + throw std::runtime_error("sign() requires a private key"); + } + + /** + * Verify a message signature against this identity + * + * @param data Data to check + * @param len Length of data + * @param signature Signature bytes + * @param siglen Length of signature in bytes + * @return True if signature validates and data integrity checks + */ + inline bool verify(const void *data,unsigned int len,const void *signature,unsigned int siglen) const + { + if (siglen != ZT_C25519_SIGNATURE_LEN) + return false; + return C25519::verify(_publicKey,data,len,signature); + } + + /** + * Verify a message signature against this identity + * + * @param data Data to check + * @param len Length of data + * @param signature Signature + * @return True if signature validates and data integrity checks + */ + inline bool verify(const void *data,unsigned int len,const C25519::Signature &signature) const + { + return C25519::verify(_publicKey,data,len,signature); + } + + /** + * Shortcut method to perform key agreement with another identity + * + * This identity must have a private key. (Check hasPrivate()) + * + * @param id Identity to agree with + * @param key Result parameter to fill with key bytes + * @param klen Length of key in bytes + * @return Was agreement successful? + */ + inline bool agree(const Identity &id,void *key,unsigned int klen) const + { + if (_privateKey) { + C25519::agree(*_privateKey,id._publicKey,key,klen); + return true; + } + return false; + } + + /** + * @return This identity's address + */ + inline const Address &address() const throw() { return _address; } + + /** + * Serialize this identity (binary) + * + * @param b Destination buffer to append to + * @param includePrivate If true, include private key component (if present) (default: false) + * @throws std::out_of_range Buffer too small + */ + template + inline void serialize(Buffer &b,bool includePrivate = false) const + { + _address.appendTo(b); + b.append((uint8_t)0); // C25519/Ed25519 identity type + b.append(_publicKey.data,(unsigned int)_publicKey.size()); + if ((_privateKey)&&(includePrivate)) { + b.append((unsigned char)_privateKey->size()); + b.append(_privateKey->data,(unsigned int)_privateKey->size()); + } else b.append((unsigned char)0); + } + + /** + * Deserialize a binary serialized identity + * + * If an exception is thrown, the Identity object is left in an undefined + * state and should not be used. + * + * @param b Buffer containing serialized data + * @param startAt Index within buffer of serialized data (default: 0) + * @return Length of serialized data read from buffer + * @throws std::out_of_range Serialized data invalid + * @throws std::invalid_argument Serialized data invalid + */ + template + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + delete _privateKey; + _privateKey = (C25519::Private *)0; + + unsigned int p = startAt; + + _address.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + p += ZT_ADDRESS_LENGTH; + + if (b[p++] != 0) + throw std::invalid_argument("unsupported identity type"); + + memcpy(_publicKey.data,b.field(p,(unsigned int)_publicKey.size()),(unsigned int)_publicKey.size()); + p += (unsigned int)_publicKey.size(); + + unsigned int privateKeyLength = (unsigned int)b[p++]; + if (privateKeyLength) { + if (privateKeyLength != ZT_C25519_PRIVATE_KEY_LEN) + throw std::invalid_argument("invalid private key"); + _privateKey = new C25519::Private(); + memcpy(_privateKey->data,b.field(p,ZT_C25519_PRIVATE_KEY_LEN),ZT_C25519_PRIVATE_KEY_LEN); + p += ZT_C25519_PRIVATE_KEY_LEN; + } + + return (p - startAt); + } + + /** + * Serialize to a more human-friendly string + * + * @param includePrivate If true, include private key (if it exists) + * @return ASCII string representation of identity + */ + std::string toString(bool includePrivate) const; + + /** + * Deserialize a human-friendly string + * + * Note: validation is for the format only. The locallyValidate() method + * must be used to check signature and address/key correspondence. + * + * @param str String to deserialize + * @return True if deserialization appears successful + */ + bool fromString(const char *str); + inline bool fromString(const std::string &str) { return fromString(str.c_str()); } + + /** + * @return C25519 public key + */ + inline const C25519::Public &publicKey() const { return _publicKey; } + + /** + * @return C25519 key pair (only returns valid pair if private key is present in this Identity object) + */ + inline const C25519::Pair privateKeyPair() const + { + C25519::Pair pair; + pair.pub = _publicKey; + if (_privateKey) + pair.priv = *_privateKey; + else memset(pair.priv.data,0,ZT_C25519_PRIVATE_KEY_LEN); + return pair; + } + + /** + * @return True if this identity contains something + */ + inline operator bool() const throw() { return (_address); } + + inline bool operator==(const Identity &id) const throw() { return ((_address == id._address)&&(_publicKey == id._publicKey)); } + inline bool operator<(const Identity &id) const throw() { return ((_address < id._address)||((_address == id._address)&&(_publicKey < id._publicKey))); } + inline bool operator!=(const Identity &id) const throw() { return !(*this == id); } + inline bool operator>(const Identity &id) const throw() { return (id < *this); } + inline bool operator<=(const Identity &id) const throw() { return !(id < *this); } + inline bool operator>=(const Identity &id) const throw() { return !(*this < id); } + +private: + Address _address; + C25519::Public _publicKey; + C25519::Private *_privateKey; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/IncomingPacket.cpp b/node/IncomingPacket.cpp new file mode 100644 index 0000000..303160e --- /dev/null +++ b/node/IncomingPacket.cpp @@ -0,0 +1,1469 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include + +#include "../version.h" +#include "../include/ZeroTierOne.h" + +#include "Constants.hpp" +#include "RuntimeEnvironment.hpp" +#include "IncomingPacket.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Peer.hpp" +#include "NetworkController.hpp" +#include "SelfAwareness.hpp" +#include "Salsa20.hpp" +#include "SHA512.hpp" +#include "World.hpp" +#include "Cluster.hpp" +#include "Node.hpp" +#include "CertificateOfMembership.hpp" +#include "CertificateOfRepresentation.hpp" +#include "Capability.hpp" +#include "Tag.hpp" +#include "Revocation.hpp" + +namespace ZeroTier { + +bool IncomingPacket::tryDecode(const RuntimeEnvironment *RR,void *tPtr) +{ + const Address sourceAddress(source()); + + try { + // Check for trusted paths or unencrypted HELLOs (HELLO is the only packet sent in the clear) + const unsigned int c = cipher(); + bool trusted = false; + if (c == ZT_PROTO_CIPHER_SUITE__NO_CRYPTO_TRUSTED_PATH) { + // If this is marked as a packet via a trusted path, check source address and path ID. + // Obviously if no trusted paths are configured this always returns false and such + // packets are dropped on the floor. + if (RR->topology->shouldInboundPathBeTrusted(_path->address(),trustedPathId())) { + trusted = true; + TRACE("TRUSTED PATH packet approved from %s(%s), trusted path ID %llx",sourceAddress.toString().c_str(),_path->address().toString().c_str(),trustedPathId()); + } else { + TRACE("dropped packet from %s(%s), cipher set to trusted path mode but path %llx@%s is not trusted!",sourceAddress.toString().c_str(),_path->address().toString().c_str(),trustedPathId(),_path->address().toString().c_str()); + return true; + } + } else if ((c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE)&&(verb() == Packet::VERB_HELLO)) { + // Only HELLO is allowed in the clear, but will still have a MAC + return _doHELLO(RR,tPtr,false); + } + + const SharedPtr peer(RR->topology->getPeer(tPtr,sourceAddress)); + if (peer) { + if (!trusted) { + if (!dearmor(peer->key())) { + //fprintf(stderr,"dropped packet from %s(%s), MAC authentication failed (size: %u)" ZT_EOL_S,sourceAddress.toString().c_str(),_path->address().toString().c_str(),size()); + TRACE("dropped packet from %s(%s), MAC authentication failed (size: %u)",sourceAddress.toString().c_str(),_path->address().toString().c_str(),size()); + return true; + } + } + + if (!uncompress()) { + //fprintf(stderr,"dropped packet from %s(%s), compressed data invalid (size %u, verb may be %u)" ZT_EOL_S,sourceAddress.toString().c_str(),_path->address().toString().c_str(),size(),(unsigned int)verb()); + TRACE("dropped packet from %s(%s), compressed data invalid (size %u, verb may be %u)",sourceAddress.toString().c_str(),_path->address().toString().c_str(),size(),(unsigned int)verb()); + return true; + } + + const Packet::Verb v = verb(); + //TRACE("<< %s from %s(%s)",Packet::verbString(v),sourceAddress.toString().c_str(),_path->address().toString().c_str()); + switch(v) { + //case Packet::VERB_NOP: + default: // ignore unknown verbs, but if they pass auth check they are "received" + peer->received(tPtr,_path,hops(),packetId(),v,0,Packet::VERB_NOP,false); + return true; + + case Packet::VERB_HELLO: return _doHELLO(RR,tPtr,true); + case Packet::VERB_ERROR: return _doERROR(RR,tPtr,peer); + case Packet::VERB_OK: return _doOK(RR,tPtr,peer); + case Packet::VERB_WHOIS: return _doWHOIS(RR,tPtr,peer); + case Packet::VERB_RENDEZVOUS: return _doRENDEZVOUS(RR,tPtr,peer); + case Packet::VERB_FRAME: return _doFRAME(RR,tPtr,peer); + case Packet::VERB_EXT_FRAME: return _doEXT_FRAME(RR,tPtr,peer); + case Packet::VERB_ECHO: return _doECHO(RR,tPtr,peer); + case Packet::VERB_MULTICAST_LIKE: return _doMULTICAST_LIKE(RR,tPtr,peer); + case Packet::VERB_NETWORK_CREDENTIALS: return _doNETWORK_CREDENTIALS(RR,tPtr,peer); + case Packet::VERB_NETWORK_CONFIG_REQUEST: return _doNETWORK_CONFIG_REQUEST(RR,tPtr,peer); + case Packet::VERB_NETWORK_CONFIG: return _doNETWORK_CONFIG(RR,tPtr,peer); + case Packet::VERB_MULTICAST_GATHER: return _doMULTICAST_GATHER(RR,tPtr,peer); + case Packet::VERB_MULTICAST_FRAME: return _doMULTICAST_FRAME(RR,tPtr,peer); + case Packet::VERB_PUSH_DIRECT_PATHS: return _doPUSH_DIRECT_PATHS(RR,tPtr,peer); + case Packet::VERB_CIRCUIT_TEST: return _doCIRCUIT_TEST(RR,tPtr,peer); + case Packet::VERB_CIRCUIT_TEST_REPORT: return _doCIRCUIT_TEST_REPORT(RR,tPtr,peer); + case Packet::VERB_USER_MESSAGE: return _doUSER_MESSAGE(RR,tPtr,peer); + } + } else { + RR->sw->requestWhois(tPtr,sourceAddress); + return false; + } + } catch ( ... ) { + // Exceptions are more informatively caught in _do...() handlers but + // this outer try/catch will catch anything else odd. + TRACE("dropped ??? from %s(%s): unexpected exception in tryDecode()",sourceAddress.toString().c_str(),_path->address().toString().c_str()); + return true; + } +} + +bool IncomingPacket::_doERROR(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + const Packet::Verb inReVerb = (Packet::Verb)(*this)[ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB]; + const uint64_t inRePacketId = at(ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID); + const Packet::ErrorCode errorCode = (Packet::ErrorCode)(*this)[ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE]; + + //TRACE("ERROR %s from %s(%s) in-re %s",Packet::errorString(errorCode),peer->address().toString().c_str(),_path->address().toString().c_str(),Packet::verbString(inReVerb)); + + /* Security note: we do not gate doERROR() with expectingReplyTo() to + * avoid having to log every outgoing packet ID. Instead we put the + * logic to determine whether we should consider an ERROR in each + * error handler. In most cases these are only trusted in specific + * circumstances. */ + + switch(errorCode) { + + case Packet::ERROR_OBJ_NOT_FOUND: + // Object not found, currently only meaningful from network controllers. + if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) { + const SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); + if ((network)&&(network->controller() == peer->address())) + network->setNotFound(); + } + break; + + case Packet::ERROR_UNSUPPORTED_OPERATION: + // This can be sent in response to any operation, though right now we only + // consider it meaningful from network controllers. This would indicate + // that the queried node does not support acting as a controller. + if (inReVerb == Packet::VERB_NETWORK_CONFIG_REQUEST) { + const SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); + if ((network)&&(network->controller() == peer->address())) + network->setNotFound(); + } + break; + + case Packet::ERROR_IDENTITY_COLLISION: + // FIXME: for federation this will need a payload with a signature or something. + if (RR->topology->isUpstream(peer->identity())) + RR->node->postEvent(tPtr,ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION); + break; + + case Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE: { + // Peers can send this in response to frames if they do not have a recent enough COM from us + const SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); + const uint64_t now = RR->node->now(); + if ( (network) && (network->config().com) && (peer->rateGateIncomingComRequest(now)) ) + network->pushCredentialsNow(tPtr,peer->address(),now); + } break; + + case Packet::ERROR_NETWORK_ACCESS_DENIED_: { + // Network controller: network access denied. + const SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); + if ((network)&&(network->controller() == peer->address())) + network->setAccessDenied(); + } break; + + case Packet::ERROR_UNWANTED_MULTICAST: { + // Members of networks can use this error to indicate that they no longer + // want to receive multicasts on a given channel. + const SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD))); + if ((network)&&(network->gate(tPtr,peer))) { + const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 8,6),6),at(ZT_PROTO_VERB_ERROR_IDX_PAYLOAD + 14)); + TRACE("%.16llx: peer %s unsubscrubed from multicast group %s",network->id(),peer->address().toString().c_str(),mg.toString().c_str()); + RR->mc->remove(network->id(),mg,peer->address()); + } + } break; + + default: break; + } + + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_ERROR,inRePacketId,inReVerb,false); + } catch ( ... ) { + TRACE("dropped ERROR from %s(%s): unexpected exception",peer->address().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool alreadyAuthenticated) +{ + try { + const uint64_t now = RR->node->now(); + + const uint64_t pid = packetId(); + const Address fromAddress(source()); + const unsigned int protoVersion = (*this)[ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION]; + const unsigned int vMajor = (*this)[ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION]; + const unsigned int vMinor = (*this)[ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION]; + const unsigned int vRevision = at(ZT_PROTO_VERB_HELLO_IDX_REVISION); + const uint64_t timestamp = at(ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP); + Identity id; + unsigned int ptr = ZT_PROTO_VERB_HELLO_IDX_IDENTITY + id.deserialize(*this,ZT_PROTO_VERB_HELLO_IDX_IDENTITY); + + if (protoVersion < ZT_PROTO_VERSION_MIN) { + TRACE("dropped HELLO from %s(%s): protocol version too old",id.address().toString().c_str(),_path->address().toString().c_str()); + return true; + } + if (fromAddress != id.address()) { + TRACE("dropped HELLO from %s(%s): identity does not match packet source address",fromAddress.toString().c_str(),_path->address().toString().c_str()); + return true; + } + + SharedPtr peer(RR->topology->getPeer(tPtr,id.address())); + if (peer) { + // We already have an identity with this address -- check for collisions + if (!alreadyAuthenticated) { + if (peer->identity() != id) { + // Identity is different from the one we already have -- address collision + + // Check rate limits + if (!RR->node->rateGateIdentityVerification(now,_path->address())) + return true; + + uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]; + if (RR->identity.agree(id,key,ZT_PEER_SECRET_KEY_LENGTH)) { + if (dearmor(key)) { // ensure packet is authentic, otherwise drop + TRACE("rejected HELLO from %s(%s): address already claimed",id.address().toString().c_str(),_path->address().toString().c_str()); + Packet outp(id.address(),RR->identity.address(),Packet::VERB_ERROR); + outp.append((uint8_t)Packet::VERB_HELLO); + outp.append((uint64_t)pid); + outp.append((uint8_t)Packet::ERROR_IDENTITY_COLLISION); + outp.armor(key,true,_path->nextOutgoingCounter()); + _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); + } else { + TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_path->address().toString().c_str()); + } + } else { + TRACE("rejected HELLO from %s(%s): key agreement failed",id.address().toString().c_str(),_path->address().toString().c_str()); + } + + return true; + } else { + // Identity is the same as the one we already have -- check packet integrity + + if (!dearmor(peer->key())) { + TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_path->address().toString().c_str()); + return true; + } + + // Continue at // VALID + } + } // else if alreadyAuthenticated then continue at // VALID + } else { + // We don't already have an identity with this address -- validate and learn it + + // Sanity check: this basically can't happen + if (alreadyAuthenticated) { + TRACE("dropped HELLO from %s(%s): somehow already authenticated with unknown peer?",id.address().toString().c_str(),_path->address().toString().c_str()); + return true; + } + + // Check rate limits + if (!RR->node->rateGateIdentityVerification(now,_path->address())) + return true; + + // Check packet integrity and MAC (this is faster than locallyValidate() so do it first to filter out total crap) + SharedPtr newPeer(new Peer(RR,RR->identity,id)); + if (!dearmor(newPeer->key())) { + TRACE("rejected HELLO from %s(%s): packet failed authentication",id.address().toString().c_str(),_path->address().toString().c_str()); + return true; + } + + // Check that identity's address is valid as per the derivation function + if (!id.locallyValidate()) { + TRACE("dropped HELLO from %s(%s): identity invalid",id.address().toString().c_str(),_path->address().toString().c_str()); + return true; + } + + peer = RR->topology->addPeer(tPtr,newPeer); + + // Continue at // VALID + } + + // VALID -- if we made it here, packet passed identity and authenticity checks! + + // Get external surface address if present (was not in old versions) + InetAddress externalSurfaceAddress; + if (ptr < size()) { + ptr += externalSurfaceAddress.deserialize(*this,ptr); + if ((externalSurfaceAddress)&&(hops() == 0)) + RR->sa->iam(tPtr,id.address(),_path->localAddress(),_path->address(),externalSurfaceAddress,RR->topology->isUpstream(id),now); + } + + // Get primary planet world ID and world timestamp if present + uint64_t planetWorldId = 0; + uint64_t planetWorldTimestamp = 0; + if ((ptr + 16) <= size()) { + planetWorldId = at(ptr); ptr += 8; + planetWorldTimestamp = at(ptr); ptr += 8; + } + + std::vector< std::pair > moonIdsAndTimestamps; + if (ptr < size()) { + // Remainder of packet, if present, is encrypted + cryptField(peer->key(),ptr,size() - ptr); + + // Get moon IDs and timestamps if present + if ((ptr + 2) <= size()) { + const unsigned int numMoons = at(ptr); ptr += 2; + for(unsigned int i=0;i(at(ptr),at(ptr + 8))); + ptr += 16; + } + } + + // Handle COR if present (older versions don't send this) + if ((ptr + 2) <= size()) { + if (at(ptr) > 0) { + CertificateOfRepresentation cor; + ptr += 2; + ptr += cor.deserialize(*this,ptr); + } else ptr += 2; + } + } + + // Send OK(HELLO) with an echo of the packet's timestamp and some of the same + // information about us: version, sent-to address, etc. + + Packet outp(id.address(),RR->identity.address(),Packet::VERB_OK); + outp.append((unsigned char)Packet::VERB_HELLO); + outp.append((uint64_t)pid); + outp.append((uint64_t)timestamp); + outp.append((unsigned char)ZT_PROTO_VERSION); + outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR); + outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR); + outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION); + + if (protoVersion >= 5) { + _path->address().serialize(outp); + } else { + /* LEGACY COMPATIBILITY HACK: + * + * For a while now (since 1.0.3), ZeroTier has recognized changes in + * its network environment empirically by examining its external network + * address as reported by trusted peers. In versions prior to 1.1.0 + * (protocol version < 5), they did this by saving a snapshot of this + * information (in SelfAwareness.hpp) keyed by reporting device ID and + * address type. + * + * This causes problems when clustering is combined with symmetric NAT. + * Symmetric NAT remaps ports, so different endpoints in a cluster will + * report back different exterior addresses. Since the old code keys + * this by device ID and not sending physical address and compares the + * entire address including port, it constantly thinks its external + * surface is changing and resets connections when talking to a cluster. + * + * In new code we key by sending physical address and device and we also + * take the more conservative position of only interpreting changes in + * IP address (neglecting port) as a change in network topology that + * necessitates a reset. But we can make older clients work here by + * nulling out the port field. Since this info is only used for empirical + * detection of link changes, it doesn't break anything else. + */ + InetAddress tmpa(_path->address()); + tmpa.setPort(0); + tmpa.serialize(outp); + } + + const unsigned int worldUpdateSizeAt = outp.size(); + outp.addSize(2); // make room for 16-bit size field + if ((planetWorldId)&&(RR->topology->planetWorldTimestamp() > planetWorldTimestamp)&&(planetWorldId == RR->topology->planetWorldId())) { + RR->topology->planet().serialize(outp,false); + } + if (moonIdsAndTimestamps.size() > 0) { + std::vector moons(RR->topology->moons()); + for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { + for(std::vector< std::pair >::const_iterator i(moonIdsAndTimestamps.begin());i!=moonIdsAndTimestamps.end();++i) { + if (i->first == m->id()) { + if (m->timestamp() > i->second) + m->serialize(outp,false); + break; + } + } + } + } + outp.setAt(worldUpdateSizeAt,(uint16_t)(outp.size() - (worldUpdateSizeAt + 2))); + + const unsigned int corSizeAt = outp.size(); + outp.addSize(2); + RR->topology->appendCertificateOfRepresentation(outp); + outp.setAt(corSizeAt,(uint16_t)(outp.size() - (corSizeAt + 2))); + + outp.armor(peer->key(),true,_path->nextOutgoingCounter()); + _path->send(RR,tPtr,outp.data(),outp.size(),now); + + peer->setRemoteVersion(protoVersion,vMajor,vMinor,vRevision); // important for this to go first so received() knows the version + peer->received(tPtr,_path,hops(),pid,Packet::VERB_HELLO,0,Packet::VERB_NOP,false); + } catch ( ... ) { + TRACE("dropped HELLO from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + const Packet::Verb inReVerb = (Packet::Verb)(*this)[ZT_PROTO_VERB_OK_IDX_IN_RE_VERB]; + const uint64_t inRePacketId = at(ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID); + + if (!RR->node->expectingReplyTo(inRePacketId)) { + TRACE("%s(%s): OK(%s) DROPPED: not expecting reply to %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),Packet::verbString(inReVerb),packetId()); + return true; + } + + //TRACE("%s(%s): OK(%s)",peer->address().toString().c_str(),_path->address().toString().c_str(),Packet::verbString(inReVerb)); + + switch(inReVerb) { + + case Packet::VERB_HELLO: { + const uint64_t latency = RR->node->now() - at(ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP); + if (latency > ZT_HELLO_MAX_ALLOWABLE_LATENCY) + return true; + + const unsigned int vProto = (*this)[ZT_PROTO_VERB_HELLO__OK__IDX_PROTOCOL_VERSION]; + const unsigned int vMajor = (*this)[ZT_PROTO_VERB_HELLO__OK__IDX_MAJOR_VERSION]; + const unsigned int vMinor = (*this)[ZT_PROTO_VERB_HELLO__OK__IDX_MINOR_VERSION]; + const unsigned int vRevision = at(ZT_PROTO_VERB_HELLO__OK__IDX_REVISION); + + if (vProto < ZT_PROTO_VERSION_MIN) { + TRACE("%s(%s): OK(HELLO) dropped, protocol version too old",source().toString().c_str(),_path->address().toString().c_str()); + return true; + } + + InetAddress externalSurfaceAddress; + unsigned int ptr = ZT_PROTO_VERB_HELLO__OK__IDX_REVISION + 2; + + // Get reported external surface address if present + if (ptr < size()) + ptr += externalSurfaceAddress.deserialize(*this,ptr); + + // Handle planet or moon updates if present + if ((ptr + 2) <= size()) { + const unsigned int worldsLen = at(ptr); ptr += 2; + if (RR->topology->shouldAcceptWorldUpdateFrom(peer->address())) { + const unsigned int endOfWorlds = ptr + worldsLen; + while (ptr < endOfWorlds) { + World w; + ptr += w.deserialize(*this,ptr); + RR->topology->addWorld(tPtr,w,false); + } + } else { + ptr += worldsLen; + } + } + + // Handle certificate of representation if present + if ((ptr + 2) <= size()) { + if (at(ptr) > 0) { + CertificateOfRepresentation cor; + ptr += 2; + ptr += cor.deserialize(*this,ptr); + } else ptr += 2; + } + +#ifdef ZT_TRACE + const std::string tmp1(source().toString()); + const std::string tmp2(_path->address().toString()); + TRACE("%s(%s): OK(HELLO), version %u.%u.%u, latency %u",tmp1.c_str(),tmp2.c_str(),vMajor,vMinor,vRevision,latency); +#endif + + if (!hops()) + peer->addDirectLatencyMeasurment((unsigned int)latency); + peer->setRemoteVersion(vProto,vMajor,vMinor,vRevision); + + if ((externalSurfaceAddress)&&(hops() == 0)) + RR->sa->iam(tPtr,peer->address(),_path->localAddress(),_path->address(),externalSurfaceAddress,RR->topology->isUpstream(peer->identity()),RR->node->now()); + } break; + + case Packet::VERB_WHOIS: + if (RR->topology->isUpstream(peer->identity())) { + const Identity id(*this,ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY); + RR->sw->doAnythingWaitingForPeer(tPtr,RR->topology->addPeer(tPtr,SharedPtr(new Peer(RR,RR->identity,id)))); + } + break; + + case Packet::VERB_NETWORK_CONFIG_REQUEST: { + const SharedPtr network(RR->node->network(at(ZT_PROTO_VERB_OK_IDX_PAYLOAD))); + if (network) + network->handleConfigChunk(tPtr,packetId(),source(),*this,ZT_PROTO_VERB_OK_IDX_PAYLOAD); + } break; + + case Packet::VERB_MULTICAST_GATHER: { + const uint64_t nwid = at(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID); + const SharedPtr network(RR->node->network(nwid)); + if (network) { + const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC,6),6),at(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_ADI)); + //TRACE("%s(%s): OK(MULTICAST_GATHER) %.16llx/%s length %u",source().toString().c_str(),_path->address().toString().c_str(),nwid,mg.toString().c_str(),size()); + const unsigned int count = at(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 4); + RR->mc->addMultiple(tPtr,RR->node->now(),nwid,mg,field(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS + 6,count * 5),count,at(ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS)); + } + } break; + + case Packet::VERB_MULTICAST_FRAME: { + const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_FLAGS]; + const uint64_t nwid = at(ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_NETWORK_ID); + const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_MAC,6),6),at(ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_ADI)); + + //TRACE("%s(%s): OK(MULTICAST_FRAME) %.16llx/%s flags %.2x",peer->address().toString().c_str(),_path->address().toString().c_str(),nwid,mg.toString().c_str(),flags); + + const SharedPtr network(RR->node->network(nwid)); + if (network) { + unsigned int offset = 0; + + if ((flags & 0x01) != 0) { // deprecated but still used by older peers + CertificateOfMembership com; + offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS); + if (com) + network->addCredential(tPtr,com); + } + + if ((flags & 0x02) != 0) { + // OK(MULTICAST_FRAME) includes implicit gather results + offset += ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS; + unsigned int totalKnown = at(offset); offset += 4; + unsigned int count = at(offset); offset += 2; + RR->mc->addMultiple(tPtr,RR->node->now(),nwid,mg,field(offset,count * 5),count,totalKnown); + } + } + } break; + + default: break; + } + + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_OK,inRePacketId,inReVerb,false); + } catch ( ... ) { + TRACE("dropped OK from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + if ((!RR->topology->amRoot())&&(!peer->rateGateInboundWhoisRequest(RR->node->now()))) { + TRACE("dropped WHOIS from %s(%s): rate limit circuit breaker tripped",source().toString().c_str(),_path->address().toString().c_str()); + return true; + } + + Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); + outp.append((unsigned char)Packet::VERB_WHOIS); + outp.append(packetId()); + + unsigned int count = 0; + unsigned int ptr = ZT_PACKET_IDX_PAYLOAD; + while ((ptr + ZT_ADDRESS_LENGTH) <= size()) { + const Address addr(field(ptr,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + ptr += ZT_ADDRESS_LENGTH; + + const Identity id(RR->topology->getIdentity(tPtr,addr)); + if (id) { + id.serialize(outp,false); + ++count; + } else { + // Request unknown WHOIS from upstream from us (if we have one) + RR->sw->requestWhois(tPtr,addr); +#ifdef ZT_ENABLE_CLUSTER + // Distribute WHOIS queries across a cluster if we do not know the ID. + // This may result in duplicate OKs to the querying peer, which is fine. + if (RR->cluster) + RR->cluster->sendDistributedQuery(*this); +#endif + } + } + + if (count > 0) { + outp.armor(peer->key(),true,_path->nextOutgoingCounter()); + _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); + } + + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_WHOIS,0,Packet::VERB_NOP,false); + } catch ( ... ) { + TRACE("dropped WHOIS from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doRENDEZVOUS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + if (!RR->topology->isUpstream(peer->identity())) { + TRACE("RENDEZVOUS from %s ignored since source is not upstream",peer->address().toString().c_str()); + } else { + const Address with(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + const SharedPtr rendezvousWith(RR->topology->getPeer(tPtr,with)); + if (rendezvousWith) { + const unsigned int port = at(ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT); + const unsigned int addrlen = (*this)[ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN]; + if ((port > 0)&&((addrlen == 4)||(addrlen == 16))) { + const InetAddress atAddr(field(ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS,addrlen),addrlen,port); + if (RR->node->shouldUsePathForZeroTierTraffic(tPtr,with,_path->localAddress(),atAddr)) { + RR->node->putPacket(tPtr,_path->localAddress(),atAddr,"ABRE",4,2); // send low-TTL junk packet to 'open' local NAT(s) and stateful firewalls + rendezvousWith->attemptToContactAt(tPtr,_path->localAddress(),atAddr,RR->node->now(),false,0); + TRACE("RENDEZVOUS from %s says %s might be at %s, sent verification attempt",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str()); + } else { + TRACE("RENDEZVOUS from %s says %s might be at %s, ignoring since path is not suitable",peer->address().toString().c_str(),with.toString().c_str(),atAddr.toString().c_str()); + } + } else { + TRACE("dropped corrupt RENDEZVOUS from %s(%s) (bad address or port)",peer->address().toString().c_str(),_path->address().toString().c_str()); + } + } else { + TRACE("ignored RENDEZVOUS from %s(%s) to meet unknown peer %s",peer->address().toString().c_str(),_path->address().toString().c_str(),with.toString().c_str()); + } + } + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_RENDEZVOUS,0,Packet::VERB_NOP,false); + } catch ( ... ) { + TRACE("dropped RENDEZVOUS from %s(%s): unexpected exception",peer->address().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doFRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + const uint64_t nwid = at(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID); + const SharedPtr network(RR->node->network(nwid)); + bool trustEstablished = false; + if (network) { + if (network->gate(tPtr,peer)) { + trustEstablished = true; + if (size() > ZT_PROTO_VERB_FRAME_IDX_PAYLOAD) { + const unsigned int etherType = at(ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE); + const MAC sourceMac(peer->address(),nwid); + const unsigned int frameLen = size() - ZT_PROTO_VERB_FRAME_IDX_PAYLOAD; + const uint8_t *const frameData = reinterpret_cast(data()) + ZT_PROTO_VERB_FRAME_IDX_PAYLOAD; + if (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),sourceMac,network->mac(),frameData,frameLen,etherType,0) > 0) + RR->node->putFrame(tPtr,nwid,network->userPtr(),sourceMac,network->mac(),etherType,0,(const void *)frameData,frameLen); + } + } else { + TRACE("dropped FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),(unsigned long long)network->id()); + _sendErrorNeedCredentials(RR,tPtr,peer,nwid); + } + } else { + TRACE("dropped FRAME from %s(%s): we are not a member of network %.16llx",source().toString().c_str(),_path->address().toString().c_str(),at(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID)); + _sendErrorNeedCredentials(RR,tPtr,peer,nwid); + } + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_FRAME,0,Packet::VERB_NOP,trustEstablished); + } catch ( ... ) { + TRACE("dropped FRAME from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + const uint64_t nwid = at(ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID); + const SharedPtr network(RR->node->network(nwid)); + if (network) { + const unsigned int flags = (*this)[ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS]; + + unsigned int comLen = 0; + if ((flags & 0x01) != 0) { // inline COM with EXT_FRAME is deprecated but still used with old peers + CertificateOfMembership com; + comLen = com.deserialize(*this,ZT_PROTO_VERB_EXT_FRAME_IDX_COM); + if (com) + network->addCredential(tPtr,com); + } + + if (!network->gate(tPtr,peer)) { + TRACE("dropped EXT_FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),network->id()); + _sendErrorNeedCredentials(RR,tPtr,peer,nwid); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,false); + return true; + } + + if (size() > ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD) { + const unsigned int etherType = at(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_ETHERTYPE); + const MAC to(field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_TO,ZT_PROTO_VERB_EXT_FRAME_LEN_TO),ZT_PROTO_VERB_EXT_FRAME_LEN_TO); + const MAC from(field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_FROM,ZT_PROTO_VERB_EXT_FRAME_LEN_FROM),ZT_PROTO_VERB_EXT_FRAME_LEN_FROM); + const unsigned int frameLen = size() - (comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD); + const uint8_t *const frameData = (const uint8_t *)field(comLen + ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD,frameLen); + + if ((!from)||(from.isMulticast())||(from == network->mac())) { + TRACE("dropped EXT_FRAME from %s@%s(%s) to %s: invalid source MAC %s",from.toString().c_str(),peer->address().toString().c_str(),_path->address().toString().c_str(),to.toString().c_str(),from.toString().c_str()); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true); // trustEstablished because COM is okay + return true; + } + + switch (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),from,to,frameData,frameLen,etherType,0)) { + case 1: + if (from != MAC(peer->address(),nwid)) { + if (network->config().permitsBridging(peer->address())) { + network->learnBridgeRoute(from,peer->address()); + } else { + TRACE("dropped EXT_FRAME from %s@%s(%s) to %s: sender not allowed to bridge into %.16llx",from.toString().c_str(),peer->address().toString().c_str(),_path->address().toString().c_str(),to.toString().c_str(),network->id()); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true); // trustEstablished because COM is okay + return true; + } + } else if (to != network->mac()) { + if (to.isMulticast()) { + if (network->config().multicastLimit == 0) { + TRACE("dropped EXT_FRAME from %s@%s(%s) to %s: network %.16llx does not allow multicast",from.toString().c_str(),peer->address().toString().c_str(),_path->address().toString().c_str(),to.toString().c_str(),network->id()); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true); // trustEstablished because COM is okay + return true; + } + } else if (!network->config().permitsBridging(RR->identity.address())) { + TRACE("dropped EXT_FRAME from %s@%s(%s) to %s: I cannot bridge to %.16llx or bridging disabled on network",from.toString().c_str(),peer->address().toString().c_str(),_path->address().toString().c_str(),to.toString().c_str(),network->id()); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true); // trustEstablished because COM is okay + return true; + } + } + // fall through -- 2 means accept regardless of bridging checks or other restrictions + case 2: + RR->node->putFrame(tPtr,nwid,network->userPtr(),from,to,etherType,0,(const void *)frameData,frameLen); + break; + } + } + + if ((flags & 0x10) != 0) { // ACK requested + Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); + outp.append((uint8_t)Packet::VERB_EXT_FRAME); + outp.append((uint64_t)packetId()); + outp.append((uint64_t)nwid); + outp.armor(peer->key(),true,_path->nextOutgoingCounter()); + _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); + } + + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,true); + } else { + TRACE("dropped EXT_FRAME from %s(%s): we are not connected to network %.16llx",source().toString().c_str(),_path->address().toString().c_str(),at(ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID)); + _sendErrorNeedCredentials(RR,tPtr,peer,nwid); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_EXT_FRAME,0,Packet::VERB_NOP,false); + } + } catch ( ... ) { + TRACE("dropped EXT_FRAME from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doECHO(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + if (!peer->rateGateEchoRequest(RR->node->now())) { + TRACE("dropped ECHO from %s(%s): rate limit circuit breaker tripped",source().toString().c_str(),_path->address().toString().c_str()); + return true; + } + + const uint64_t pid = packetId(); + Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); + outp.append((unsigned char)Packet::VERB_ECHO); + outp.append((uint64_t)pid); + if (size() > ZT_PACKET_IDX_PAYLOAD) + outp.append(reinterpret_cast(data()) + ZT_PACKET_IDX_PAYLOAD,size() - ZT_PACKET_IDX_PAYLOAD); + outp.armor(peer->key(),true,_path->nextOutgoingCounter()); + _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); + + peer->received(tPtr,_path,hops(),pid,Packet::VERB_ECHO,0,Packet::VERB_NOP,false); + } catch ( ... ) { + TRACE("dropped ECHO from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doMULTICAST_LIKE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + const uint64_t now = RR->node->now(); + + uint64_t authOnNetwork[256]; // cache for approved network IDs + unsigned int authOnNetworkCount = 0; + SharedPtr network; + bool trustEstablished = false; + + // Iterate through 18-byte network,MAC,ADI tuples + for(unsigned int ptr=ZT_PACKET_IDX_PAYLOAD;ptr(ptr); + + bool auth = false; + for(unsigned int i=0;iid() != nwid)) + network = RR->node->network(nwid); + const bool authOnNet = ((network)&&(network->gate(tPtr,peer))); + if (!authOnNet) + _sendErrorNeedCredentials(RR,tPtr,peer,nwid); + trustEstablished |= authOnNet; + if (authOnNet||RR->mc->cacheAuthorized(peer->address(),nwid,now)) { + auth = true; + if (authOnNetworkCount < 256) // sanity check, packets can't really be this big + authOnNetwork[authOnNetworkCount++] = nwid; + } + } + + if (auth) { + const MulticastGroup group(MAC(field(ptr + 8,6),6),at(ptr + 14)); + RR->mc->add(tPtr,now,nwid,group,peer->address()); + } + } + + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_MULTICAST_LIKE,0,Packet::VERB_NOP,trustEstablished); + } catch ( ... ) { + TRACE("dropped MULTICAST_LIKE from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + if (!peer->rateGateCredentialsReceived(RR->node->now())) { + TRACE("dropped NETWORK_CREDENTIALS from %s(%s): rate limit circuit breaker tripped",source().toString().c_str(),_path->address().toString().c_str()); + return true; + } + + CertificateOfMembership com; + Capability cap; + Tag tag; + Revocation revocation; + CertificateOfOwnership coo; + bool trustEstablished = false; + + unsigned int p = ZT_PACKET_IDX_PAYLOAD; + while ((p < size())&&((*this)[p] != 0)) { + p += com.deserialize(*this,p); + if (com) { + const SharedPtr network(RR->node->network(com.networkId())); + if (network) { + switch (network->addCredential(tPtr,com)) { + case Membership::ADD_REJECTED: + break; + case Membership::ADD_ACCEPTED_NEW: + case Membership::ADD_ACCEPTED_REDUNDANT: + trustEstablished = true; + break; + case Membership::ADD_DEFERRED_FOR_WHOIS: + return false; + } + } else RR->mc->addCredential(tPtr,com,false); + } + } + ++p; // skip trailing 0 after COMs if present + + if (p < size()) { // older ZeroTier versions do not send capabilities, tags, or revocations + const unsigned int numCapabilities = at(p); p += 2; + for(unsigned int i=0;i network(RR->node->network(cap.networkId())); + if (network) { + switch (network->addCredential(tPtr,cap)) { + case Membership::ADD_REJECTED: + break; + case Membership::ADD_ACCEPTED_NEW: + case Membership::ADD_ACCEPTED_REDUNDANT: + trustEstablished = true; + break; + case Membership::ADD_DEFERRED_FOR_WHOIS: + return false; + } + } + } + + if (p >= size()) return true; + + const unsigned int numTags = at(p); p += 2; + for(unsigned int i=0;i network(RR->node->network(tag.networkId())); + if (network) { + switch (network->addCredential(tPtr,tag)) { + case Membership::ADD_REJECTED: + break; + case Membership::ADD_ACCEPTED_NEW: + case Membership::ADD_ACCEPTED_REDUNDANT: + trustEstablished = true; + break; + case Membership::ADD_DEFERRED_FOR_WHOIS: + return false; + } + } + } + + if (p >= size()) return true; + + const unsigned int numRevocations = at(p); p += 2; + for(unsigned int i=0;i network(RR->node->network(revocation.networkId())); + if (network) { + switch(network->addCredential(tPtr,peer->address(),revocation)) { + case Membership::ADD_REJECTED: + break; + case Membership::ADD_ACCEPTED_NEW: + case Membership::ADD_ACCEPTED_REDUNDANT: + trustEstablished = true; + break; + case Membership::ADD_DEFERRED_FOR_WHOIS: + return false; + } + } + } + + if (p >= size()) return true; + + const unsigned int numCoos = at(p); p += 2; + for(unsigned int i=0;i network(RR->node->network(coo.networkId())); + if (network) { + switch(network->addCredential(tPtr,coo)) { + case Membership::ADD_REJECTED: + break; + case Membership::ADD_ACCEPTED_NEW: + case Membership::ADD_ACCEPTED_REDUNDANT: + trustEstablished = true; + break; + case Membership::ADD_DEFERRED_FOR_WHOIS: + return false; + } + } + } + } + + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_NETWORK_CREDENTIALS,0,Packet::VERB_NOP,trustEstablished); + } catch (std::exception &exc) { + //fprintf(stderr,"dropped NETWORK_CREDENTIALS from %s(%s): %s" ZT_EOL_S,source().toString().c_str(),_path->address().toString().c_str(),exc.what()); + TRACE("dropped NETWORK_CREDENTIALS from %s(%s): %s",source().toString().c_str(),_path->address().toString().c_str(),exc.what()); + } catch ( ... ) { + //fprintf(stderr,"dropped NETWORK_CREDENTIALS from %s(%s): unknown exception" ZT_EOL_S,source().toString().c_str(),_path->address().toString().c_str()); + TRACE("dropped NETWORK_CREDENTIALS from %s(%s): unknown exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + const uint64_t nwid = at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID); + const unsigned int hopCount = hops(); + const uint64_t requestPacketId = packetId(); + + if (RR->localNetworkController) { + const unsigned int metaDataLength = (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN <= size()) ? at(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN) : 0; + const char *metaDataBytes = (metaDataLength != 0) ? (const char *)field(ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT,metaDataLength) : (const char *)0; + const Dictionary metaData(metaDataBytes,metaDataLength); + RR->localNetworkController->request(nwid,(hopCount > 0) ? InetAddress() : _path->address(),requestPacketId,peer->identity(),metaData); + } else { + Packet outp(peer->address(),RR->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(requestPacketId); + outp.append((unsigned char)Packet::ERROR_UNSUPPORTED_OPERATION); + outp.append(nwid); + outp.armor(peer->key(),true,_path->nextOutgoingCounter()); + _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); + } + + peer->received(tPtr,_path,hopCount,requestPacketId,Packet::VERB_NETWORK_CONFIG_REQUEST,0,Packet::VERB_NOP,false); + } catch (std::exception &exc) { + //fprintf(stderr,"dropped NETWORK_CONFIG_REQUEST from %s(%s): %s" ZT_EOL_S,source().toString().c_str(),_path->address().toString().c_str(),exc.what()); + TRACE("dropped NETWORK_CONFIG_REQUEST from %s(%s): %s",source().toString().c_str(),_path->address().toString().c_str(),exc.what()); + } catch ( ... ) { + //fprintf(stderr,"dropped NETWORK_CONFIG_REQUEST from %s(%s): unknown exception" ZT_EOL_S,source().toString().c_str(),_path->address().toString().c_str()); + TRACE("dropped NETWORK_CONFIG_REQUEST from %s(%s): unknown exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + const SharedPtr network(RR->node->network(at(ZT_PACKET_IDX_PAYLOAD))); + if (network) { + const uint64_t configUpdateId = network->handleConfigChunk(tPtr,packetId(),source(),*this,ZT_PACKET_IDX_PAYLOAD); + if (configUpdateId) { + Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); + outp.append((uint8_t)Packet::VERB_ECHO); + outp.append((uint64_t)packetId()); + outp.append((uint64_t)network->id()); + outp.append((uint64_t)configUpdateId); + outp.armor(peer->key(),true,_path->nextOutgoingCounter()); + _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); + } + } + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_NETWORK_CONFIG,0,Packet::VERB_NOP,false); + } catch ( ... ) { + TRACE("dropped NETWORK_CONFIG_REFRESH from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + const uint64_t nwid = at(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID); + const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_GATHER_IDX_FLAGS]; + const MulticastGroup mg(MAC(field(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC,6),6),at(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_ADI)); + const unsigned int gatherLimit = at(ZT_PROTO_VERB_MULTICAST_GATHER_IDX_GATHER_LIMIT); + + //TRACE("<address().toString().c_str(),gatherLimit,nwid,mg.toString().c_str()); + + const SharedPtr network(RR->node->network(nwid)); + + if ((flags & 0x01) != 0) { + try { + CertificateOfMembership com; + com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_GATHER_IDX_COM); + if (com) { + if (network) + network->addCredential(tPtr,com); + else RR->mc->addCredential(tPtr,com,false); + } + } catch ( ... ) { + TRACE("MULTICAST_GATHER from %s(%s): discarded invalid COM",peer->address().toString().c_str(),_path->address().toString().c_str()); + } + } + + const bool trustEstablished = ((network)&&(network->gate(tPtr,peer))); + if (!trustEstablished) + _sendErrorNeedCredentials(RR,tPtr,peer,nwid); + if ( ( trustEstablished || RR->mc->cacheAuthorized(peer->address(),nwid,RR->node->now()) ) && (gatherLimit > 0) ) { + Packet outp(peer->address(),RR->identity.address(),Packet::VERB_OK); + outp.append((unsigned char)Packet::VERB_MULTICAST_GATHER); + outp.append(packetId()); + outp.append(nwid); + mg.mac().appendTo(outp); + outp.append((uint32_t)mg.adi()); + const unsigned int gatheredLocally = RR->mc->gather(peer->address(),nwid,mg,outp,gatherLimit); + if (gatheredLocally > 0) { + outp.armor(peer->key(),true,_path->nextOutgoingCounter()); + _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); + } + + // If we are a member of a cluster, distribute this GATHER across it +#ifdef ZT_ENABLE_CLUSTER + if ((RR->cluster)&&(gatheredLocally < gatherLimit)) + RR->cluster->sendDistributedQuery(*this); +#endif + } + + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_MULTICAST_GATHER,0,Packet::VERB_NOP,trustEstablished); + } catch ( ... ) { + TRACE("dropped MULTICAST_GATHER from %s(%s): unexpected exception",peer->address().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + const uint64_t nwid = at(ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID); + const unsigned int flags = (*this)[ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS]; + + const SharedPtr network(RR->node->network(nwid)); + if (network) { + // Offset -- size of optional fields added to position of later fields + unsigned int offset = 0; + + if ((flags & 0x01) != 0) { + // This is deprecated but may still be sent by old peers + CertificateOfMembership com; + offset += com.deserialize(*this,ZT_PROTO_VERB_MULTICAST_FRAME_IDX_COM); + if (com) + network->addCredential(tPtr,com); + } + + if (!network->gate(tPtr,peer)) { + TRACE("dropped MULTICAST_FRAME from %s(%s): not a member of private network %.16llx",peer->address().toString().c_str(),_path->address().toString().c_str(),(unsigned long long)network->id()); + _sendErrorNeedCredentials(RR,tPtr,peer,nwid); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,false); + return true; + } + + if (network->config().multicastLimit == 0) { + TRACE("dropped MULTICAST_FRAME from %s(%s): network %.16llx does not allow multicast",peer->address().toString().c_str(),_path->address().toString().c_str(),(unsigned long long)network->id()); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,false); + return true; + } + + unsigned int gatherLimit = 0; + if ((flags & 0x02) != 0) { + gatherLimit = at(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_GATHER_LIMIT); + offset += 4; + } + + MAC from; + if ((flags & 0x04) != 0) { + from.setTo(field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SOURCE_MAC,6),6); + offset += 6; + } else { + from.fromAddress(peer->address(),nwid); + } + + const MulticastGroup to(MAC(field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_MAC,6),6),at(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_ADI)); + const unsigned int etherType = at(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE); + const unsigned int frameLen = size() - (offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME); + + //TRACE("<address().toString().c_str(),flags,frameLen); + + if ((frameLen > 0)&&(frameLen <= ZT_IF_MTU)) { + if (!to.mac().isMulticast()) { + TRACE("dropped MULTICAST_FRAME from %s@%s(%s) to %s: destination is unicast, must use FRAME or EXT_FRAME",from.toString().c_str(),peer->address().toString().c_str(),_path->address().toString().c_str(),to.toString().c_str()); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,true); // trustEstablished because COM is okay + return true; + } + if ((!from)||(from.isMulticast())||(from == network->mac())) { + TRACE("dropped MULTICAST_FRAME from %s@%s(%s) to %s: invalid source MAC",from.toString().c_str(),peer->address().toString().c_str(),_path->address().toString().c_str(),to.toString().c_str()); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,true); // trustEstablished because COM is okay + return true; + } + + if (from != MAC(peer->address(),nwid)) { + if (network->config().permitsBridging(peer->address())) { + network->learnBridgeRoute(from,peer->address()); + } else { + TRACE("dropped MULTICAST_FRAME from %s@%s(%s) to %s: sender not allowed to bridge into %.16llx",from.toString().c_str(),peer->address().toString().c_str(),_path->address().toString().c_str(),to.toString().c_str(),network->id()); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,true); // trustEstablished because COM is okay + return true; + } + } + + const uint8_t *const frameData = (const uint8_t *)field(offset + ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME,frameLen); + if (network->filterIncomingPacket(tPtr,peer,RR->identity.address(),from,to.mac(),frameData,frameLen,etherType,0) > 0) { + RR->node->putFrame(tPtr,nwid,network->userPtr(),from,to.mac(),etherType,0,(const void *)frameData,frameLen); + } + } + + if (gatherLimit) { + Packet outp(source(),RR->identity.address(),Packet::VERB_OK); + outp.append((unsigned char)Packet::VERB_MULTICAST_FRAME); + outp.append(packetId()); + outp.append(nwid); + to.mac().appendTo(outp); + outp.append((uint32_t)to.adi()); + outp.append((unsigned char)0x02); // flag 0x02 = contains gather results + if (RR->mc->gather(peer->address(),nwid,to,outp,gatherLimit)) { + outp.armor(peer->key(),true,_path->nextOutgoingCounter()); + _path->send(RR,tPtr,outp.data(),outp.size(),RR->node->now()); + } + } + + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,true); + } else { + _sendErrorNeedCredentials(RR,tPtr,peer,nwid); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_MULTICAST_FRAME,0,Packet::VERB_NOP,false); + } + } catch ( ... ) { + TRACE("dropped MULTICAST_FRAME from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + const uint64_t now = RR->node->now(); + + // First, subject this to a rate limit + if (!peer->rateGatePushDirectPaths(now)) { + TRACE("dropped PUSH_DIRECT_PATHS from %s(%s): circuit breaker tripped",source().toString().c_str(),_path->address().toString().c_str()); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_PUSH_DIRECT_PATHS,0,Packet::VERB_NOP,false); + return true; + } + + // Second, limit addresses by scope and type + uint8_t countPerScope[ZT_INETADDRESS_MAX_SCOPE+1][2]; // [][0] is v4, [][1] is v6 + memset(countPerScope,0,sizeof(countPerScope)); + + unsigned int count = at(ZT_PACKET_IDX_PAYLOAD); + unsigned int ptr = ZT_PACKET_IDX_PAYLOAD + 2; + + while (count--) { // if ptr overflows Buffer will throw + // TODO: some flags are not yet implemented + + unsigned int flags = (*this)[ptr++]; + unsigned int extLen = at(ptr); ptr += 2; + ptr += extLen; // unused right now + unsigned int addrType = (*this)[ptr++]; + unsigned int addrLen = (*this)[ptr++]; + + switch(addrType) { + case 4: { + const InetAddress a(field(ptr,4),4,at(ptr + 4)); + if ( + ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) && // not being told to forget + (!( ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) == 0) && (peer->hasActivePathTo(now,a)) )) && // not already known + (RR->node->shouldUsePathForZeroTierTraffic(tPtr,peer->address(),_path->localAddress(),a)) ) // should use path + { + if ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) != 0) + peer->setClusterPreferred(a); + if (++countPerScope[(int)a.ipScope()][0] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) { + TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str()); + peer->attemptToContactAt(tPtr,InetAddress(),a,now,false,0); + } else { + TRACE("ignoring contact for %s at %s -- too many per scope",peer->address().toString().c_str(),a.toString().c_str()); + } + } + } break; + case 6: { + const InetAddress a(field(ptr,16),16,at(ptr + 16)); + if ( + ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH) == 0) && // not being told to forget + (!( ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) == 0) && (peer->hasActivePathTo(now,a)) )) && // not already known + (RR->node->shouldUsePathForZeroTierTraffic(tPtr,peer->address(),_path->localAddress(),a)) ) // should use path + { + if ((flags & ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT) != 0) + peer->setClusterPreferred(a); + if (++countPerScope[(int)a.ipScope()][1] <= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) { + TRACE("attempting to contact %s at pushed direct path %s",peer->address().toString().c_str(),a.toString().c_str()); + peer->attemptToContactAt(tPtr,InetAddress(),a,now,false,0); + } else { + TRACE("ignoring contact for %s at %s -- too many per scope",peer->address().toString().c_str(),a.toString().c_str()); + } + } + } break; + } + ptr += addrLen; + } + + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_PUSH_DIRECT_PATHS,0,Packet::VERB_NOP,false); + } catch ( ... ) { + TRACE("dropped PUSH_DIRECT_PATHS from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doCIRCUIT_TEST(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + const Address originatorAddress(field(ZT_PACKET_IDX_PAYLOAD,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); + SharedPtr originator(RR->topology->getPeer(tPtr,originatorAddress)); + if (!originator) { + RR->sw->requestWhois(tPtr,originatorAddress); + return false; + } + + const unsigned int flags = at(ZT_PACKET_IDX_PAYLOAD + 5); + const uint64_t timestamp = at(ZT_PACKET_IDX_PAYLOAD + 7); + const uint64_t testId = at(ZT_PACKET_IDX_PAYLOAD + 15); + + // Tracks total length of variable length fields, initialized to originator credential length below + unsigned int vlf; + + // Originator credentials -- right now only a network ID for which the originator is controller or is authorized by controller is allowed + const unsigned int originatorCredentialLength = vlf = at(ZT_PACKET_IDX_PAYLOAD + 23); + uint64_t originatorCredentialNetworkId = 0; + if (originatorCredentialLength >= 1) { + switch((*this)[ZT_PACKET_IDX_PAYLOAD + 25]) { + case 0x01: { // 64-bit network ID, originator must be controller + if (originatorCredentialLength >= 9) + originatorCredentialNetworkId = at(ZT_PACKET_IDX_PAYLOAD + 26); + } break; + default: break; + } + } + + // Add length of "additional fields," which are currently unused + vlf += at(ZT_PACKET_IDX_PAYLOAD + 25 + vlf); + + // Verify signature -- only tests signed by their originators are allowed + const unsigned int signatureLength = at(ZT_PACKET_IDX_PAYLOAD + 27 + vlf); + if (!originator->identity().verify(field(ZT_PACKET_IDX_PAYLOAD,27 + vlf),27 + vlf,field(ZT_PACKET_IDX_PAYLOAD + 29 + vlf,signatureLength),signatureLength)) { + TRACE("dropped CIRCUIT_TEST from %s(%s): signature by originator %s invalid",source().toString().c_str(),_path->address().toString().c_str(),originatorAddress.toString().c_str()); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_CIRCUIT_TEST,0,Packet::VERB_NOP,false); + return true; + } + vlf += signatureLength; + + // Save this length so we can copy the immutable parts of this test + // into the one we send along to next hops. + const unsigned int lengthOfSignedPortionAndSignature = 29 + vlf; + + // Add length of second "additional fields" section. + vlf += at(ZT_PACKET_IDX_PAYLOAD + 29 + vlf); + + uint64_t reportFlags = 0; + + // Check credentials (signature already verified) + if (originatorCredentialNetworkId) { + SharedPtr network(RR->node->network(originatorCredentialNetworkId)); + if ((!network)||(!network->config().circuitTestingAllowed(originatorAddress))) { + TRACE("dropped CIRCUIT_TEST from %s(%s): originator %s specified network ID %.16llx as credential, and we don't belong to that network or originator is not allowed'",source().toString().c_str(),_path->address().toString().c_str(),originatorAddress.toString().c_str(),originatorCredentialNetworkId); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_CIRCUIT_TEST,0,Packet::VERB_NOP,false); + return true; + } + if (network->gate(tPtr,peer)) + reportFlags |= ZT_CIRCUIT_TEST_REPORT_FLAGS_UPSTREAM_AUTHORIZED_IN_PATH; + } else { + TRACE("dropped CIRCUIT_TEST from %s(%s): originator %s did not specify a credential or credential type",source().toString().c_str(),_path->address().toString().c_str(),originatorAddress.toString().c_str()); + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_CIRCUIT_TEST,0,Packet::VERB_NOP,false); + return true; + } + + const uint64_t now = RR->node->now(); + + unsigned int breadth = 0; + Address nextHop[256]; // breadth is a uin8_t, so this is the max + InetAddress nextHopBestPathAddress[256]; + unsigned int remainingHopsPtr = ZT_PACKET_IDX_PAYLOAD + 33 + vlf; + if ((ZT_PACKET_IDX_PAYLOAD + 31 + vlf) < size()) { + // unsigned int nextHopFlags = (*this)[ZT_PACKET_IDX_PAYLOAD + 31 + vlf] + breadth = (*this)[ZT_PACKET_IDX_PAYLOAD + 32 + vlf]; + for(unsigned int h=0;h nhp(RR->topology->getPeer(tPtr,nextHop[h])); + if (nhp) { + SharedPtr nhbp(nhp->getBestPath(now,false)); + if ((nhbp)&&(nhbp->alive(now))) + nextHopBestPathAddress[h] = nhbp->address(); + } + } + } + + // Report back to originator, depending on flags and whether we are last hop + if ( ((flags & 0x01) != 0) || ((breadth == 0)&&((flags & 0x02) != 0)) ) { + Packet outp(originatorAddress,RR->identity.address(),Packet::VERB_CIRCUIT_TEST_REPORT); + outp.append((uint64_t)timestamp); + outp.append((uint64_t)testId); + outp.append((uint64_t)0); // field reserved for future use + outp.append((uint8_t)ZT_VENDOR_ZEROTIER); + outp.append((uint8_t)ZT_PROTO_VERSION); + outp.append((uint8_t)ZEROTIER_ONE_VERSION_MAJOR); + outp.append((uint8_t)ZEROTIER_ONE_VERSION_MINOR); + outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION); + outp.append((uint16_t)ZT_PLATFORM_UNSPECIFIED); + outp.append((uint16_t)ZT_ARCHITECTURE_UNSPECIFIED); + outp.append((uint16_t)0); // error code, currently unused + outp.append((uint64_t)reportFlags); + outp.append((uint64_t)packetId()); + peer->address().appendTo(outp); + outp.append((uint8_t)hops()); + _path->localAddress().serialize(outp); + _path->address().serialize(outp); + outp.append((uint16_t)_path->linkQuality()); + outp.append((uint8_t)breadth); + for(unsigned int h=0;hsw->send(tPtr,outp,true); + } + + // If there are next hops, forward the test along through the graph + if (breadth > 0) { + Packet outp(Address(),RR->identity.address(),Packet::VERB_CIRCUIT_TEST); + outp.append(field(ZT_PACKET_IDX_PAYLOAD,lengthOfSignedPortionAndSignature),lengthOfSignedPortionAndSignature); + outp.append((uint16_t)0); // no additional fields + if (remainingHopsPtr < size()) + outp.append(field(remainingHopsPtr,size() - remainingHopsPtr),size() - remainingHopsPtr); + + for(unsigned int h=0;hidentity.address() != nextHop[h]) { // next hops that loop back to the current hop are not valid + outp.newInitializationVector(); + outp.setDestination(nextHop[h]); + RR->sw->send(tPtr,outp,true); + } + } + } + + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_CIRCUIT_TEST,0,Packet::VERB_NOP,false); + } catch ( ... ) { + TRACE("dropped CIRCUIT_TEST from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doCIRCUIT_TEST_REPORT(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + ZT_CircuitTestReport report; + memset(&report,0,sizeof(report)); + + report.current = peer->address().toInt(); + report.upstream = Address(field(ZT_PACKET_IDX_PAYLOAD + 52,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH).toInt(); + report.testId = at(ZT_PACKET_IDX_PAYLOAD + 8); + report.timestamp = at(ZT_PACKET_IDX_PAYLOAD); + report.sourcePacketId = at(ZT_PACKET_IDX_PAYLOAD + 44); + report.flags = at(ZT_PACKET_IDX_PAYLOAD + 36); + report.sourcePacketHopCount = (*this)[ZT_PACKET_IDX_PAYLOAD + 57]; // end of fixed length headers: 58 + report.errorCode = at(ZT_PACKET_IDX_PAYLOAD + 34); + report.vendor = (enum ZT_Vendor)((*this)[ZT_PACKET_IDX_PAYLOAD + 24]); + report.protocolVersion = (*this)[ZT_PACKET_IDX_PAYLOAD + 25]; + report.majorVersion = (*this)[ZT_PACKET_IDX_PAYLOAD + 26]; + report.minorVersion = (*this)[ZT_PACKET_IDX_PAYLOAD + 27]; + report.revision = at(ZT_PACKET_IDX_PAYLOAD + 28); + report.platform = (enum ZT_Platform)at(ZT_PACKET_IDX_PAYLOAD + 30); + report.architecture = (enum ZT_Architecture)at(ZT_PACKET_IDX_PAYLOAD + 32); + + const unsigned int receivedOnLocalAddressLen = reinterpret_cast(&(report.receivedOnLocalAddress))->deserialize(*this,ZT_PACKET_IDX_PAYLOAD + 58); + const unsigned int receivedFromRemoteAddressLen = reinterpret_cast(&(report.receivedFromRemoteAddress))->deserialize(*this,ZT_PACKET_IDX_PAYLOAD + 58 + receivedOnLocalAddressLen); + unsigned int ptr = ZT_PACKET_IDX_PAYLOAD + 58 + receivedOnLocalAddressLen + receivedFromRemoteAddressLen; + if (report.protocolVersion >= 9) { + report.receivedFromLinkQuality = at(ptr); ptr += 2; + } else { + report.receivedFromLinkQuality = ZT_PATH_LINK_QUALITY_MAX; + ptr += at(ptr) + 2; // this field was once an 'extended field length' reserved field, which was always set to 0 + } + + report.nextHopCount = (*this)[ptr++]; + if (report.nextHopCount > ZT_CIRCUIT_TEST_MAX_HOP_BREADTH) // sanity check, shouldn't be possible + report.nextHopCount = ZT_CIRCUIT_TEST_MAX_HOP_BREADTH; + for(unsigned int h=0;h(&(report.nextHops[h].physicalAddress))->deserialize(*this,ptr); + } + + RR->node->postCircuitTestReport(&report); + + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_CIRCUIT_TEST_REPORT,0,Packet::VERB_NOP,false); + } catch ( ... ) { + TRACE("dropped CIRCUIT_TEST_REPORT from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +bool IncomingPacket::_doUSER_MESSAGE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer) +{ + try { + if (size() >= (ZT_PACKET_IDX_PAYLOAD + 8)) { + ZT_UserMessage um; + um.origin = peer->address().toInt(); + um.typeId = at(ZT_PACKET_IDX_PAYLOAD); + um.data = reinterpret_cast(reinterpret_cast(data()) + ZT_PACKET_IDX_PAYLOAD + 8); + um.length = size() - (ZT_PACKET_IDX_PAYLOAD + 8); + RR->node->postEvent(tPtr,ZT_EVENT_USER_MESSAGE,reinterpret_cast(&um)); + } + peer->received(tPtr,_path,hops(),packetId(),Packet::VERB_CIRCUIT_TEST_REPORT,0,Packet::VERB_NOP,false); + } catch ( ... ) { + TRACE("dropped CIRCUIT_TEST_REPORT from %s(%s): unexpected exception",source().toString().c_str(),_path->address().toString().c_str()); + } + return true; +} + +void IncomingPacket::_sendErrorNeedCredentials(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,const uint64_t nwid) +{ + const uint64_t now = RR->node->now(); + if (peer->rateGateOutgoingComRequest(now)) { + Packet outp(source(),RR->identity.address(),Packet::VERB_ERROR); + outp.append((uint8_t)verb()); + outp.append(packetId()); + outp.append((uint8_t)Packet::ERROR_NEED_MEMBERSHIP_CERTIFICATE); + outp.append(nwid); + outp.armor(peer->key(),true,_path->nextOutgoingCounter()); + _path->send(RR,tPtr,outp.data(),outp.size(),now); + } +} + +} // namespace ZeroTier diff --git a/node/IncomingPacket.hpp b/node/IncomingPacket.hpp new file mode 100644 index 0000000..3d4a2e0 --- /dev/null +++ b/node/IncomingPacket.hpp @@ -0,0 +1,145 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_INCOMINGPACKET_HPP +#define ZT_INCOMINGPACKET_HPP + +#include + +#include "Packet.hpp" +#include "Path.hpp" +#include "Utils.hpp" +#include "MulticastGroup.hpp" +#include "Peer.hpp" + +/* + * The big picture: + * + * tryDecode gets called for a given fully-assembled packet until it returns + * true or the packet's time to live has been exceeded, in which case it is + * discarded as failed decode. Any exception thrown by tryDecode also causes + * the packet to be discarded. + * + * Thus a return of false from tryDecode() indicates that it should be called + * again. Logic is very simple as to when, and it's in doAnythingWaitingForPeer + * in Switch. This might be expanded to be more fine grained in the future. + * + * A return value of true indicates that the packet is done. tryDecode must + * never be called again after that. + */ + +namespace ZeroTier { + +class RuntimeEnvironment; +class Network; + +/** + * Subclass of packet that handles the decoding of it + */ +class IncomingPacket : public Packet +{ +public: + IncomingPacket() : + Packet(), + _receiveTime(0) + { + } + + /** + * Create a new packet-in-decode + * + * @param data Packet data + * @param len Packet length + * @param path Path over which packet arrived + * @param now Current time + * @throws std::out_of_range Range error processing packet + */ + IncomingPacket(const void *data,unsigned int len,const SharedPtr &path,uint64_t now) : + Packet(data,len), + _receiveTime(now), + _path(path) + { + } + + /** + * Init packet-in-decode in place + * + * @param data Packet data + * @param len Packet length + * @param path Path over which packet arrived + * @param now Current time + * @throws std::out_of_range Range error processing packet + */ + inline void init(const void *data,unsigned int len,const SharedPtr &path,uint64_t now) + { + copyFrom(data,len); + _receiveTime = now; + _path = path; + } + + /** + * Attempt to decode this packet + * + * Note that this returns 'true' if processing is complete. This says nothing + * about whether the packet was valid. A rejection is 'complete.' + * + * Once true is returned, this must not be called again. The packet's state + * may no longer be valid. + * + * @param RR Runtime environment + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @return True if decoding and processing is complete, false if caller should try again + */ + bool tryDecode(const RuntimeEnvironment *RR,void *tPtr); + + /** + * @return Time of packet receipt / start of decode + */ + inline uint64_t receiveTime() const throw() { return _receiveTime; } + +private: + // These are called internally to handle packet contents once it has + // been authenticated, decrypted, decompressed, and classified. + bool _doERROR(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doHELLO(const RuntimeEnvironment *RR,void *tPtr,const bool alreadyAuthenticated); + bool _doOK(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doWHOIS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doRENDEZVOUS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doFRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doEXT_FRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doECHO(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doMULTICAST_LIKE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doNETWORK_CREDENTIALS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doNETWORK_CONFIG_REQUEST(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doNETWORK_CONFIG(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doMULTICAST_GATHER(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doMULTICAST_FRAME(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doPUSH_DIRECT_PATHS(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doCIRCUIT_TEST(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doCIRCUIT_TEST_REPORT(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + bool _doUSER_MESSAGE(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer); + + void _sendErrorNeedCredentials(const RuntimeEnvironment *RR,void *tPtr,const SharedPtr &peer,const uint64_t nwid); + + uint64_t _receiveTime; + SharedPtr _path; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/InetAddress.cpp b/node/InetAddress.cpp new file mode 100644 index 0000000..7d22eea --- /dev/null +++ b/node/InetAddress.cpp @@ -0,0 +1,473 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include + +#include + +#include "Constants.hpp" +#include "InetAddress.hpp" +#include "Utils.hpp" + +namespace ZeroTier { + +const InetAddress InetAddress::LO4((const void *)("\x7f\x00\x00\x01"),4,0); +const InetAddress InetAddress::LO6((const void *)("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"),16,0); + +InetAddress::IpScope InetAddress::ipScope() const + throw() +{ + switch(ss_family) { + + case AF_INET: { + const uint32_t ip = Utils::ntoh((uint32_t)reinterpret_cast(this)->sin_addr.s_addr); + switch(ip >> 24) { + case 0x00: return IP_SCOPE_NONE; // 0.0.0.0/8 (reserved, never used) + case 0x06: return IP_SCOPE_PSEUDOPRIVATE; // 6.0.0.0/8 (US Army) + case 0x0a: return IP_SCOPE_PRIVATE; // 10.0.0.0/8 + case 0x0b: return IP_SCOPE_PSEUDOPRIVATE; // 11.0.0.0/8 (US DoD) + case 0x15: return IP_SCOPE_PSEUDOPRIVATE; // 21.0.0.0/8 (US DDN-RVN) + case 0x16: return IP_SCOPE_PSEUDOPRIVATE; // 22.0.0.0/8 (US DISA) + case 0x19: return IP_SCOPE_PSEUDOPRIVATE; // 25.0.0.0/8 (UK Ministry of Defense) + case 0x1a: return IP_SCOPE_PSEUDOPRIVATE; // 26.0.0.0/8 (US DISA) + case 0x1c: return IP_SCOPE_PSEUDOPRIVATE; // 28.0.0.0/8 (US DSI-North) + case 0x1d: return IP_SCOPE_PSEUDOPRIVATE; // 29.0.0.0/8 (US DISA) + case 0x1e: return IP_SCOPE_PSEUDOPRIVATE; // 30.0.0.0/8 (US DISA) + case 0x2c: return IP_SCOPE_PSEUDOPRIVATE; // 44.0.0.0/8 (Amateur Radio) + case 0x33: return IP_SCOPE_PSEUDOPRIVATE; // 51.0.0.0/8 (UK Department of Social Security) + case 0x37: return IP_SCOPE_PSEUDOPRIVATE; // 55.0.0.0/8 (US DoD) + case 0x38: return IP_SCOPE_PSEUDOPRIVATE; // 56.0.0.0/8 (US Postal Service) + case 0x64: + if ((ip & 0xffc00000) == 0x64400000) return IP_SCOPE_SHARED; // 100.64.0.0/10 + break; + case 0x7f: return IP_SCOPE_LOOPBACK; // 127.0.0.0/8 + case 0xa9: + if ((ip & 0xffff0000) == 0xa9fe0000) return IP_SCOPE_LINK_LOCAL; // 169.254.0.0/16 + break; + case 0xac: + if ((ip & 0xfff00000) == 0xac100000) return IP_SCOPE_PRIVATE; // 172.16.0.0/12 + break; + case 0xc0: + if ((ip & 0xffff0000) == 0xc0a80000) return IP_SCOPE_PRIVATE; // 192.168.0.0/16 + break; + case 0xff: return IP_SCOPE_NONE; // 255.0.0.0/8 (broadcast, or unused/unusable) + } + switch(ip >> 28) { + case 0xe: return IP_SCOPE_MULTICAST; // 224.0.0.0/4 + case 0xf: return IP_SCOPE_PSEUDOPRIVATE; // 240.0.0.0/4 ("reserved," usually unusable) + } + return IP_SCOPE_GLOBAL; + } break; + + case AF_INET6: { + const unsigned char *ip = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); + if ((ip[0] & 0xf0) == 0xf0) { + if (ip[0] == 0xff) return IP_SCOPE_MULTICAST; // ff00::/8 + if ((ip[0] == 0xfe)&&((ip[1] & 0xc0) == 0x80)) { + unsigned int k = 2; + while ((!ip[k])&&(k < 15)) ++k; + if ((k == 15)&&(ip[15] == 0x01)) + return IP_SCOPE_LOOPBACK; // fe80::1/128 + else return IP_SCOPE_LINK_LOCAL; // fe80::/10 + } + if ((ip[0] & 0xfe) == 0xfc) return IP_SCOPE_PRIVATE; // fc00::/7 + } + unsigned int k = 0; + while ((!ip[k])&&(k < 15)) ++k; + if (k == 15) { // all 0's except last byte + if (ip[15] == 0x01) return IP_SCOPE_LOOPBACK; // ::1/128 + if (ip[15] == 0x00) return IP_SCOPE_NONE; // ::/128 + } + return IP_SCOPE_GLOBAL; + } break; + + } + + return IP_SCOPE_NONE; +} + +void InetAddress::set(const std::string &ip,unsigned int port) + throw() +{ + memset(this,0,sizeof(InetAddress)); + if (ip.find(':') != std::string::npos) { + struct sockaddr_in6 *sin6 = reinterpret_cast(this); + ss_family = AF_INET6; + sin6->sin6_port = Utils::hton((uint16_t)port); + if (inet_pton(AF_INET6,ip.c_str(),(void *)&(sin6->sin6_addr.s6_addr)) <= 0) + memset(this,0,sizeof(InetAddress)); + } else if (ip.find('.') != std::string::npos) { + struct sockaddr_in *sin = reinterpret_cast(this); + ss_family = AF_INET; + sin->sin_port = Utils::hton((uint16_t)port); + if (inet_pton(AF_INET,ip.c_str(),(void *)&(sin->sin_addr.s_addr)) <= 0) + memset(this,0,sizeof(InetAddress)); + } +} + +void InetAddress::set(const void *ipBytes,unsigned int ipLen,unsigned int port) + throw() +{ + memset(this,0,sizeof(InetAddress)); + if (ipLen == 4) { + uint32_t ipb[1]; + memcpy(ipb,ipBytes,4); + ss_family = AF_INET; + reinterpret_cast(this)->sin_addr.s_addr = ipb[0]; + reinterpret_cast(this)->sin_port = Utils::hton((uint16_t)port); + } else if (ipLen == 16) { + ss_family = AF_INET6; + memcpy(reinterpret_cast(this)->sin6_addr.s6_addr,ipBytes,16); + reinterpret_cast(this)->sin6_port = Utils::hton((uint16_t)port); + } +} + +std::string InetAddress::toString() const +{ + char buf[128]; + switch(ss_family) { + case AF_INET: + Utils::snprintf(buf,sizeof(buf),"%d.%d.%d.%d/%d", + (int)(reinterpret_cast(&(reinterpret_cast(this)->sin_addr.s_addr)))[0], + (int)(reinterpret_cast(&(reinterpret_cast(this)->sin_addr.s_addr)))[1], + (int)(reinterpret_cast(&(reinterpret_cast(this)->sin_addr.s_addr)))[2], + (int)(reinterpret_cast(&(reinterpret_cast(this)->sin_addr.s_addr)))[3], + (int)Utils::ntoh((uint16_t)(reinterpret_cast(this)->sin_port)) + ); + return std::string(buf); + case AF_INET6: + Utils::snprintf(buf,sizeof(buf),"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x/%d", + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[0]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[1]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[2]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[3]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[4]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[5]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[6]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[7]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[8]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[9]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[10]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[11]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[12]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[13]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[14]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[15]), + (int)Utils::ntoh((uint16_t)(reinterpret_cast(this)->sin6_port)) + ); + return std::string(buf); + } + return std::string(); +} + +std::string InetAddress::toIpString() const +{ + char buf[128]; + switch(ss_family) { + case AF_INET: + Utils::snprintf(buf,sizeof(buf),"%d.%d.%d.%d", + (int)(reinterpret_cast(&(reinterpret_cast(this)->sin_addr.s_addr)))[0], + (int)(reinterpret_cast(&(reinterpret_cast(this)->sin_addr.s_addr)))[1], + (int)(reinterpret_cast(&(reinterpret_cast(this)->sin_addr.s_addr)))[2], + (int)(reinterpret_cast(&(reinterpret_cast(this)->sin_addr.s_addr)))[3] + ); + return std::string(buf); + case AF_INET6: + Utils::snprintf(buf,sizeof(buf),"%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[0]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[1]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[2]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[3]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[4]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[5]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[6]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[7]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[8]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[9]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[10]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[11]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[12]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[13]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[14]), + (int)(reinterpret_cast(this)->sin6_addr.s6_addr[15]) + ); + return std::string(buf); + } + return std::string(); +} + +void InetAddress::fromString(const std::string &ipSlashPort) +{ + const std::size_t slashAt = ipSlashPort.find('/'); + if (slashAt == std::string::npos) { + set(ipSlashPort,0); + } else { + long p = strtol(ipSlashPort.substr(slashAt+1).c_str(),(char **)0,10); + if ((p > 0)&&(p <= 0xffff)) + set(ipSlashPort.substr(0,slashAt),(unsigned int)p); + else set(ipSlashPort.substr(0,slashAt),0); + } +} + +InetAddress InetAddress::netmask() const +{ + InetAddress r(*this); + switch(r.ss_family) { + case AF_INET: + reinterpret_cast(&r)->sin_addr.s_addr = Utils::hton((uint32_t)(0xffffffff << (32 - netmaskBits()))); + break; + case AF_INET6: { + uint64_t nm[2]; + const unsigned int bits = netmaskBits(); + if(bits) { + nm[0] = Utils::hton((uint64_t)((bits >= 64) ? 0xffffffffffffffffULL : (0xffffffffffffffffULL << (64 - bits)))); + nm[1] = Utils::hton((uint64_t)((bits <= 64) ? 0ULL : (0xffffffffffffffffULL << (128 - bits)))); + } + else { + nm[0] = 0; + nm[1] = 0; + } + memcpy(reinterpret_cast(&r)->sin6_addr.s6_addr,nm,16); + } break; + } + return r; +} + +InetAddress InetAddress::broadcast() const +{ + if (ss_family == AF_INET) { + InetAddress r(*this); + reinterpret_cast(&r)->sin_addr.s_addr |= Utils::hton((uint32_t)(0xffffffff >> netmaskBits())); + return r; + } + return InetAddress(); +} + +InetAddress InetAddress::network() const +{ + InetAddress r(*this); + switch(r.ss_family) { + case AF_INET: + reinterpret_cast(&r)->sin_addr.s_addr &= Utils::hton((uint32_t)(0xffffffff << (32 - netmaskBits()))); + break; + case AF_INET6: { + uint64_t nm[2]; + const unsigned int bits = netmaskBits(); + memcpy(nm,reinterpret_cast(&r)->sin6_addr.s6_addr,16); + nm[0] &= Utils::hton((uint64_t)((bits >= 64) ? 0xffffffffffffffffULL : (0xffffffffffffffffULL << (64 - bits)))); + nm[1] &= Utils::hton((uint64_t)((bits <= 64) ? 0ULL : (0xffffffffffffffffULL << (128 - bits)))); + memcpy(reinterpret_cast(&r)->sin6_addr.s6_addr,nm,16); + } break; + } + return r; +} + +bool InetAddress::containsAddress(const InetAddress &addr) const +{ + if (addr.ss_family == ss_family) { + switch(ss_family) { + case AF_INET: { + const unsigned int bits = netmaskBits(); + if (bits == 0) + return true; + return ( (Utils::ntoh((uint32_t)reinterpret_cast(&addr)->sin_addr.s_addr) >> (32 - bits)) == (Utils::ntoh((uint32_t)reinterpret_cast(this)->sin_addr.s_addr) >> (32 - bits)) ); + } + case AF_INET6: { + const InetAddress mask(netmask()); + const uint8_t *m = reinterpret_cast(reinterpret_cast(&mask)->sin6_addr.s6_addr); + const uint8_t *a = reinterpret_cast(reinterpret_cast(&addr)->sin6_addr.s6_addr); + const uint8_t *b = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); + for(unsigned int i=0;i<16;++i) { + if ((a[i] & m[i]) != b[i]) + return false; + } + return true; + } + } + } + return false; +} + +bool InetAddress::isNetwork() const + throw() +{ + switch(ss_family) { + case AF_INET: { + unsigned int bits = netmaskBits(); + if (bits <= 0) + return false; + if (bits >= 32) + return false; + uint32_t ip = Utils::ntoh((uint32_t)reinterpret_cast(this)->sin_addr.s_addr); + return ((ip & (0xffffffff >> bits)) == 0); + } + case AF_INET6: { + unsigned int bits = netmaskBits(); + if (bits <= 0) + return false; + if (bits >= 128) + return false; + const unsigned char *ip = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); + unsigned int p = bits / 8; + if ((ip[p++] & (0xff >> (bits % 8))) != 0) + return false; + while (p < 16) { + if (ip[p++]) + return false; + } + return true; + } + } + return false; +} + +bool InetAddress::operator==(const InetAddress &a) const + throw() +{ + if (ss_family == a.ss_family) { + switch(ss_family) { + case AF_INET: + return ( + (reinterpret_cast(this)->sin_port == reinterpret_cast(&a)->sin_port)&& + (reinterpret_cast(this)->sin_addr.s_addr == reinterpret_cast(&a)->sin_addr.s_addr)); + break; + case AF_INET6: + return ( + (reinterpret_cast(this)->sin6_port == reinterpret_cast(&a)->sin6_port)&& + (reinterpret_cast(this)->sin6_flowinfo == reinterpret_cast(&a)->sin6_flowinfo)&& + (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) == 0)&& + (reinterpret_cast(this)->sin6_scope_id == reinterpret_cast(&a)->sin6_scope_id)); + break; + default: + return (memcmp(this,&a,sizeof(InetAddress)) == 0); + } + } + return false; +} + +bool InetAddress::operator<(const InetAddress &a) const + throw() +{ + if (ss_family < a.ss_family) + return true; + else if (ss_family == a.ss_family) { + switch(ss_family) { + case AF_INET: + if (reinterpret_cast(this)->sin_port < reinterpret_cast(&a)->sin_port) + return true; + else if (reinterpret_cast(this)->sin_port == reinterpret_cast(&a)->sin_port) { + if (reinterpret_cast(this)->sin_addr.s_addr < reinterpret_cast(&a)->sin_addr.s_addr) + return true; + } + break; + case AF_INET6: + if (reinterpret_cast(this)->sin6_port < reinterpret_cast(&a)->sin6_port) + return true; + else if (reinterpret_cast(this)->sin6_port == reinterpret_cast(&a)->sin6_port) { + if (reinterpret_cast(this)->sin6_flowinfo < reinterpret_cast(&a)->sin6_flowinfo) + return true; + else if (reinterpret_cast(this)->sin6_flowinfo == reinterpret_cast(&a)->sin6_flowinfo) { + if (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) < 0) + return true; + else if (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) == 0) { + if (reinterpret_cast(this)->sin6_scope_id < reinterpret_cast(&a)->sin6_scope_id) + return true; + } + } + } + break; + default: + return (memcmp(this,&a,sizeof(InetAddress)) < 0); + } + } + return false; +} + +InetAddress InetAddress::makeIpv6LinkLocal(const MAC &mac) +{ + struct sockaddr_in6 sin6; + sin6.sin6_family = AF_INET6; + sin6.sin6_addr.s6_addr[0] = 0xfe; + sin6.sin6_addr.s6_addr[1] = 0x80; + sin6.sin6_addr.s6_addr[2] = 0x00; + sin6.sin6_addr.s6_addr[3] = 0x00; + sin6.sin6_addr.s6_addr[4] = 0x00; + sin6.sin6_addr.s6_addr[5] = 0x00; + sin6.sin6_addr.s6_addr[6] = 0x00; + sin6.sin6_addr.s6_addr[7] = 0x00; + sin6.sin6_addr.s6_addr[8] = mac[0] & 0xfd; + sin6.sin6_addr.s6_addr[9] = mac[1]; + sin6.sin6_addr.s6_addr[10] = mac[2]; + sin6.sin6_addr.s6_addr[11] = 0xff; + sin6.sin6_addr.s6_addr[12] = 0xfe; + sin6.sin6_addr.s6_addr[13] = mac[3]; + sin6.sin6_addr.s6_addr[14] = mac[4]; + sin6.sin6_addr.s6_addr[15] = mac[5]; + sin6.sin6_port = Utils::hton((uint16_t)64); + return InetAddress(sin6); +} + +InetAddress InetAddress::makeIpv6rfc4193(uint64_t nwid,uint64_t zeroTierAddress) +{ + InetAddress r; + struct sockaddr_in6 *const sin6 = reinterpret_cast(&r); + sin6->sin6_family = AF_INET6; + sin6->sin6_addr.s6_addr[0] = 0xfd; + sin6->sin6_addr.s6_addr[1] = (uint8_t)(nwid >> 56); + sin6->sin6_addr.s6_addr[2] = (uint8_t)(nwid >> 48); + sin6->sin6_addr.s6_addr[3] = (uint8_t)(nwid >> 40); + sin6->sin6_addr.s6_addr[4] = (uint8_t)(nwid >> 32); + sin6->sin6_addr.s6_addr[5] = (uint8_t)(nwid >> 24); + sin6->sin6_addr.s6_addr[6] = (uint8_t)(nwid >> 16); + sin6->sin6_addr.s6_addr[7] = (uint8_t)(nwid >> 8); + sin6->sin6_addr.s6_addr[8] = (uint8_t)nwid; + sin6->sin6_addr.s6_addr[9] = 0x99; + sin6->sin6_addr.s6_addr[10] = 0x93; + sin6->sin6_addr.s6_addr[11] = (uint8_t)(zeroTierAddress >> 32); + sin6->sin6_addr.s6_addr[12] = (uint8_t)(zeroTierAddress >> 24); + sin6->sin6_addr.s6_addr[13] = (uint8_t)(zeroTierAddress >> 16); + sin6->sin6_addr.s6_addr[14] = (uint8_t)(zeroTierAddress >> 8); + sin6->sin6_addr.s6_addr[15] = (uint8_t)zeroTierAddress; + sin6->sin6_port = Utils::hton((uint16_t)88); // /88 includes 0xfd + network ID, discriminating by device ID below that + return r; +} + +InetAddress InetAddress::makeIpv66plane(uint64_t nwid,uint64_t zeroTierAddress) +{ + nwid ^= (nwid >> 32); + InetAddress r; + struct sockaddr_in6 *const sin6 = reinterpret_cast(&r); + sin6->sin6_family = AF_INET6; + sin6->sin6_addr.s6_addr[0] = 0xfc; + sin6->sin6_addr.s6_addr[1] = (uint8_t)(nwid >> 24); + sin6->sin6_addr.s6_addr[2] = (uint8_t)(nwid >> 16); + sin6->sin6_addr.s6_addr[3] = (uint8_t)(nwid >> 8); + sin6->sin6_addr.s6_addr[4] = (uint8_t)nwid; + sin6->sin6_addr.s6_addr[5] = (uint8_t)(zeroTierAddress >> 32); + sin6->sin6_addr.s6_addr[6] = (uint8_t)(zeroTierAddress >> 24); + sin6->sin6_addr.s6_addr[7] = (uint8_t)(zeroTierAddress >> 16); + sin6->sin6_addr.s6_addr[8] = (uint8_t)(zeroTierAddress >> 8); + sin6->sin6_addr.s6_addr[9] = (uint8_t)zeroTierAddress; + sin6->sin6_addr.s6_addr[15] = 0x01; + sin6->sin6_port = Utils::hton((uint16_t)40); + return r; +} + +} // namespace ZeroTier diff --git a/node/InetAddress.hpp b/node/InetAddress.hpp new file mode 100644 index 0000000..c37fa62 --- /dev/null +++ b/node/InetAddress.hpp @@ -0,0 +1,601 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_INETADDRESS_HPP +#define ZT_INETADDRESS_HPP + +#include +#include +#include + +#include + +#include "Constants.hpp" +#include "../include/ZeroTierOne.h" +#include "Utils.hpp" +#include "MAC.hpp" +#include "Buffer.hpp" + +namespace ZeroTier { + +/** + * Maximum integer value of enum IpScope + */ +#define ZT_INETADDRESS_MAX_SCOPE 7 + +/** + * Extends sockaddr_storage with friendly C++ methods + * + * This is basically a "mixin" for sockaddr_storage. It adds methods and + * operators, but does not modify the structure. This can be cast to/from + * sockaddr_storage and used interchangeably. DO NOT change this by e.g. + * adding non-static fields, since much code depends on this identity. + */ +struct InetAddress : public sockaddr_storage +{ + /** + * Loopback IPv4 address (no port) + */ + static const InetAddress LO4; + + /** + * Loopback IPV6 address (no port) + */ + static const InetAddress LO6; + + /** + * IP address scope + * + * Note that these values are in ascending order of path preference and + * MUST remain that way or Path must be changed to reflect. Also be sure + * to change ZT_INETADDRESS_MAX_SCOPE if the max changes. + */ + enum IpScope + { + IP_SCOPE_NONE = 0, // NULL or not an IP address + IP_SCOPE_MULTICAST = 1, // 224.0.0.0 and other V4/V6 multicast IPs + IP_SCOPE_LOOPBACK = 2, // 127.0.0.1, ::1, etc. + IP_SCOPE_PSEUDOPRIVATE = 3, // 28.x.x.x, etc. -- unofficially unrouted IPv4 blocks often "bogarted" + IP_SCOPE_GLOBAL = 4, // globally routable IP address (all others) + IP_SCOPE_LINK_LOCAL = 5, // 169.254.x.x, IPv6 LL + IP_SCOPE_SHARED = 6, // 100.64.0.0/10, shared space for e.g. carrier-grade NAT + IP_SCOPE_PRIVATE = 7 // 10.x.x.x, 192.168.x.x, etc. + }; + + InetAddress() throw() { memset(this,0,sizeof(InetAddress)); } + InetAddress(const InetAddress &a) throw() { memcpy(this,&a,sizeof(InetAddress)); } + InetAddress(const InetAddress *a) throw() { memcpy(this,a,sizeof(InetAddress)); } + InetAddress(const struct sockaddr_storage &ss) throw() { *this = ss; } + InetAddress(const struct sockaddr_storage *ss) throw() { *this = ss; } + InetAddress(const struct sockaddr &sa) throw() { *this = sa; } + InetAddress(const struct sockaddr *sa) throw() { *this = sa; } + InetAddress(const struct sockaddr_in &sa) throw() { *this = sa; } + InetAddress(const struct sockaddr_in *sa) throw() { *this = sa; } + InetAddress(const struct sockaddr_in6 &sa) throw() { *this = sa; } + InetAddress(const struct sockaddr_in6 *sa) throw() { *this = sa; } + InetAddress(const void *ipBytes,unsigned int ipLen,unsigned int port) throw() { this->set(ipBytes,ipLen,port); } + InetAddress(const uint32_t ipv4,unsigned int port) throw() { this->set(&ipv4,4,port); } + InetAddress(const std::string &ip,unsigned int port) throw() { this->set(ip,port); } + InetAddress(const std::string &ipSlashPort) throw() { this->fromString(ipSlashPort); } + InetAddress(const char *ipSlashPort) throw() { this->fromString(std::string(ipSlashPort)); } + + inline InetAddress &operator=(const InetAddress &a) + throw() + { + if (&a != this) + memcpy(this,&a,sizeof(InetAddress)); + return *this; + } + + inline InetAddress &operator=(const InetAddress *a) + throw() + { + if (a != this) + memcpy(this,a,sizeof(InetAddress)); + return *this; + } + + inline InetAddress &operator=(const struct sockaddr_storage &ss) + throw() + { + if (reinterpret_cast(&ss) != this) + memcpy(this,&ss,sizeof(InetAddress)); + return *this; + } + + inline InetAddress &operator=(const struct sockaddr_storage *ss) + throw() + { + if (reinterpret_cast(ss) != this) + memcpy(this,ss,sizeof(InetAddress)); + return *this; + } + + inline InetAddress &operator=(const struct sockaddr_in &sa) + throw() + { + if (reinterpret_cast(&sa) != this) { + memset(this,0,sizeof(InetAddress)); + memcpy(this,&sa,sizeof(struct sockaddr_in)); + } + return *this; + } + + inline InetAddress &operator=(const struct sockaddr_in *sa) + throw() + { + if (reinterpret_cast(sa) != this) { + memset(this,0,sizeof(InetAddress)); + memcpy(this,sa,sizeof(struct sockaddr_in)); + } + return *this; + } + + inline InetAddress &operator=(const struct sockaddr_in6 &sa) + throw() + { + if (reinterpret_cast(&sa) != this) { + memset(this,0,sizeof(InetAddress)); + memcpy(this,&sa,sizeof(struct sockaddr_in6)); + } + return *this; + } + + inline InetAddress &operator=(const struct sockaddr_in6 *sa) + throw() + { + if (reinterpret_cast(sa) != this) { + memset(this,0,sizeof(InetAddress)); + memcpy(this,sa,sizeof(struct sockaddr_in6)); + } + return *this; + } + + inline InetAddress &operator=(const struct sockaddr &sa) + throw() + { + if (reinterpret_cast(&sa) != this) { + memset(this,0,sizeof(InetAddress)); + switch(sa.sa_family) { + case AF_INET: + memcpy(this,&sa,sizeof(struct sockaddr_in)); + break; + case AF_INET6: + memcpy(this,&sa,sizeof(struct sockaddr_in6)); + break; + } + } + return *this; + } + + inline InetAddress &operator=(const struct sockaddr *sa) + throw() + { + if (reinterpret_cast(sa) != this) { + memset(this,0,sizeof(InetAddress)); + switch(sa->sa_family) { + case AF_INET: + memcpy(this,sa,sizeof(struct sockaddr_in)); + break; + case AF_INET6: + memcpy(this,sa,sizeof(struct sockaddr_in6)); + break; + } + } + return *this; + } + + /** + * @return IP scope classification (e.g. loopback, link-local, private, global) + */ + IpScope ipScope() const + throw(); + + /** + * Set from a string-format IP and a port + * + * @param ip IP address in V4 or V6 ASCII notation + * @param port Port or 0 for none + */ + void set(const std::string &ip,unsigned int port) + throw(); + + /** + * Set from a raw IP and port number + * + * @param ipBytes Bytes of IP address in network byte order + * @param ipLen Length of IP address: 4 or 16 + * @param port Port number or 0 for none + */ + void set(const void *ipBytes,unsigned int ipLen,unsigned int port) + throw(); + + /** + * Set the port component + * + * @param port Port, 0 to 65535 + */ + inline void setPort(unsigned int port) + { + switch(ss_family) { + case AF_INET: + reinterpret_cast(this)->sin_port = Utils::hton((uint16_t)port); + break; + case AF_INET6: + reinterpret_cast(this)->sin6_port = Utils::hton((uint16_t)port); + break; + } + } + + /** + * @return True if this network/netmask route describes a default route (e.g. 0.0.0.0/0) + */ + inline bool isDefaultRoute() const + { + switch(ss_family) { + case AF_INET: + return ( (reinterpret_cast(this)->sin_addr.s_addr == 0) && (reinterpret_cast(this)->sin_port == 0) ); + case AF_INET6: + const uint8_t *ipb = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); + for(int i=0;i<16;++i) { + if (ipb[i]) + return false; + } + return (reinterpret_cast(this)->sin6_port == 0); + } + return false; + } + + /** + * @return ASCII IP/port format representation + */ + std::string toString() const; + + /** + * @return IP portion only, in ASCII string format + */ + std::string toIpString() const; + + /** + * @param ipSlashPort ASCII IP/port format notation + */ + void fromString(const std::string &ipSlashPort); + + /** + * @return Port or 0 if no port component defined + */ + inline unsigned int port() const + throw() + { + switch(ss_family) { + case AF_INET: return Utils::ntoh((uint16_t)(reinterpret_cast(this)->sin_port)); + case AF_INET6: return Utils::ntoh((uint16_t)(reinterpret_cast(this)->sin6_port)); + default: return 0; + } + } + + /** + * Alias for port() + * + * This just aliases port() to make code more readable when netmask bits + * are stuffed there, as they are in Network, EthernetTap, and a few other + * spots. + * + * @return Netmask bits + */ + inline unsigned int netmaskBits() const throw() { return port(); } + + /** + * @return True if netmask bits is valid for the address type + */ + inline bool netmaskBitsValid() const + { + const unsigned int n = port(); + switch(ss_family) { + case AF_INET: return (n <= 32); + case AF_INET6: return (n <= 128); + } + return false; + } + + /** + * Alias for port() + * + * This just aliases port() because for gateways we use this field to + * store the gateway metric. + * + * @return Gateway metric + */ + inline unsigned int metric() const throw() { return port(); } + + /** + * Construct a full netmask as an InetAddress + * + * @return Netmask such as 255.255.255.0 if this address is /24 (port field will be unchanged) + */ + InetAddress netmask() const; + + /** + * Constructs a broadcast address from a network/netmask address + * + * This is only valid for IPv4 and will return a NULL InetAddress for other + * address families. + * + * @return Broadcast address (only IP portion is meaningful) + */ + InetAddress broadcast() const; + + /** + * Return the network -- a.k.a. the IP ANDed with the netmask + * + * @return Network e.g. 10.0.1.0/24 from 10.0.1.200/24 + */ + InetAddress network() const; + + /** + * Test whether this IP/netmask contains this address + * + * @param addr Address to check + * @return True if this IP/netmask (route) contains this address + */ + bool containsAddress(const InetAddress &addr) const; + + /** + * @return True if this is an IPv4 address + */ + inline bool isV4() const throw() { return (ss_family == AF_INET); } + + /** + * @return True if this is an IPv6 address + */ + inline bool isV6() const throw() { return (ss_family == AF_INET6); } + + /** + * @return pointer to raw address bytes or NULL if not available + */ + inline const void *rawIpData() const + { + switch(ss_family) { + case AF_INET: return (const void *)&(reinterpret_cast(this)->sin_addr.s_addr); + case AF_INET6: return (const void *)(reinterpret_cast(this)->sin6_addr.s6_addr); + default: return 0; + } + } + + /** + * @return InetAddress containing only the IP portion of this address and a zero port, or NULL if not IPv4 or IPv6 + */ + inline InetAddress ipOnly() const + { + InetAddress r; + switch(ss_family) { + case AF_INET: + r.ss_family = AF_INET; + reinterpret_cast(&r)->sin_addr.s_addr = reinterpret_cast(this)->sin_addr.s_addr; + break; + case AF_INET6: + r.ss_family = AF_INET6; + memcpy(reinterpret_cast(&r)->sin6_addr.s6_addr,reinterpret_cast(this)->sin6_addr.s6_addr,16); + break; + } + return r; + } + + /** + * Performs an IP-only comparison or, if that is impossible, a memcmp() + * + * @param a InetAddress to compare again + * @return True if only IP portions are equal (false for non-IP or null addresses) + */ + inline bool ipsEqual(const InetAddress &a) const + { + if (ss_family == a.ss_family) { + if (ss_family == AF_INET) + return (reinterpret_cast(this)->sin_addr.s_addr == reinterpret_cast(&a)->sin_addr.s_addr); + if (ss_family == AF_INET6) + return (memcmp(reinterpret_cast(this)->sin6_addr.s6_addr,reinterpret_cast(&a)->sin6_addr.s6_addr,16) == 0); + return (memcmp(this,&a,sizeof(InetAddress)) == 0); + } + return false; + } + + inline unsigned long hashCode() const + { + if (ss_family == AF_INET) { + return ((unsigned long)reinterpret_cast(this)->sin_addr.s_addr + (unsigned long)reinterpret_cast(this)->sin_port); + } else if (ss_family == AF_INET6) { + unsigned long tmp = reinterpret_cast(this)->sin6_port; + const uint8_t *a = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); + for(long i=0;i<16;++i) + reinterpret_cast(&tmp)[i % sizeof(tmp)] ^= a[i]; + return tmp; + } else { + unsigned long tmp = reinterpret_cast(this)->sin6_port; + const uint8_t *a = reinterpret_cast(this); + for(long i=0;i<(long)sizeof(InetAddress);++i) + reinterpret_cast(&tmp)[i % sizeof(tmp)] ^= a[i]; + return tmp; + } + } + + /** + * Set to null/zero + */ + inline void zero() throw() { memset(this,0,sizeof(InetAddress)); } + + /** + * Check whether this is a network/route rather than an IP assignment + * + * A network is an IP/netmask where everything after the netmask is + * zero e.g. 10.0.0.0/8. + * + * @return True if everything after netmask bits is zero + */ + bool isNetwork() const + throw(); + + /** + * @return 14-bit (0-16383) hash of this IP's first 24 or 48 bits (for V4 or V6) for rate limiting code, or 0 if non-IP + */ + inline unsigned long rateGateHash() const + { + unsigned long h = 0; + switch(ss_family) { + case AF_INET: + h = (Utils::ntoh((uint32_t)reinterpret_cast(this)->sin_addr.s_addr) & 0xffffff00) >> 8; + h ^= (h >> 14); + break; + case AF_INET6: { + const uint8_t *ip = reinterpret_cast(reinterpret_cast(this)->sin6_addr.s6_addr); + h = ((unsigned long)ip[0]); h <<= 1; + h += ((unsigned long)ip[1]); h <<= 1; + h += ((unsigned long)ip[2]); h <<= 1; + h += ((unsigned long)ip[3]); h <<= 1; + h += ((unsigned long)ip[4]); h <<= 1; + h += ((unsigned long)ip[5]); + } break; + } + return (h & 0x3fff); + } + + /** + * @return True if address family is non-zero + */ + inline operator bool() const throw() { return (ss_family != 0); } + + template + inline void serialize(Buffer &b) const + { + // This is used in the protocol and must be the same as describe in places + // like VERB_HELLO in Packet.hpp. + switch(ss_family) { + case AF_INET: + b.append((uint8_t)0x04); + b.append(&(reinterpret_cast(this)->sin_addr.s_addr),4); + b.append((uint16_t)port()); // just in case sin_port != uint16_t + return; + case AF_INET6: + b.append((uint8_t)0x06); + b.append(reinterpret_cast(this)->sin6_addr.s6_addr,16); + b.append((uint16_t)port()); // just in case sin_port != uint16_t + return; + default: + b.append((uint8_t)0); + return; + } + } + + template + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + memset(this,0,sizeof(InetAddress)); + unsigned int p = startAt; + switch(b[p++]) { + case 0: + return 1; + case 0x01: + // TODO: Ethernet address (but accept for forward compatibility) + return 7; + case 0x02: + // TODO: Bluetooth address (but accept for forward compatibility) + return 7; + case 0x03: + // TODO: Other address types (but accept for forward compatibility) + // These could be extended/optional things like AF_UNIX, LTE Direct, shared memory, etc. + return (unsigned int)(b.template at(p) + 3); // other addresses begin with 16-bit non-inclusive length + case 0x04: + ss_family = AF_INET; + memcpy(&(reinterpret_cast(this)->sin_addr.s_addr),b.field(p,4),4); p += 4; + reinterpret_cast(this)->sin_port = Utils::hton(b.template at(p)); p += 2; + break; + case 0x06: + ss_family = AF_INET6; + memcpy(reinterpret_cast(this)->sin6_addr.s6_addr,b.field(p,16),16); p += 16; + reinterpret_cast(this)->sin_port = Utils::hton(b.template at(p)); p += 2; + break; + default: + throw std::invalid_argument("invalid serialized InetAddress"); + } + return (p - startAt); + } + + bool operator==(const InetAddress &a) const throw(); + bool operator<(const InetAddress &a) const throw(); + inline bool operator!=(const InetAddress &a) const throw() { return !(*this == a); } + inline bool operator>(const InetAddress &a) const throw() { return (a < *this); } + inline bool operator<=(const InetAddress &a) const throw() { return !(a < *this); } + inline bool operator>=(const InetAddress &a) const throw() { return !(*this < a); } + + /** + * @param mac MAC address seed + * @return IPv6 link-local address + */ + static InetAddress makeIpv6LinkLocal(const MAC &mac); + + /** + * Compute private IPv6 unicast address from network ID and ZeroTier address + * + * This generates a private unicast IPv6 address that is mostly compliant + * with the letter of RFC4193 and certainly compliant in spirit. + * + * RFC4193 specifies a format of: + * + * | 7 bits |1| 40 bits | 16 bits | 64 bits | + * | Prefix |L| Global ID | Subnet ID | Interface ID | + * + * The 'L' bit is set to 1, yielding an address beginning with 0xfd. Then + * the network ID is filled into the global ID, subnet ID, and first byte + * of the "interface ID" field. Since the first 40 bits of the network ID + * is the unique ZeroTier address of its controller, this makes a very + * good random global ID. Since network IDs have 24 more bits, we let it + * overflow into the interface ID. + * + * After that we pad with two bytes: 0x99, 0x93, namely the default ZeroTier + * port in hex. + * + * Finally we fill the remaining 40 bits of the interface ID field with + * the 40-bit unique ZeroTier device ID of the network member. + * + * This yields a valid RFC4193 address with a random global ID, a + * meaningful subnet ID, and a unique interface ID, all mappable back onto + * ZeroTier space. + * + * This in turn could allow us, on networks numbered this way, to emulate + * IPv6 NDP and eliminate all multicast. This could be beneficial for + * small devices and huge networks, e.g. IoT applications. + * + * The returned address is given an odd prefix length of /88, since within + * a given network only the last 40 bits (device ID) are variable. This + * is a bit unusual but as far as we know should not cause any problems with + * any non-braindead IPv6 stack. + * + * @param nwid 64-bit network ID + * @param zeroTierAddress 40-bit device address (in least significant 40 bits, highest 24 bits ignored) + * @return IPv6 private unicast address with /88 netmask + */ + static InetAddress makeIpv6rfc4193(uint64_t nwid,uint64_t zeroTierAddress); + + /** + * Compute a private IPv6 "6plane" unicast address from network ID and ZeroTier address + */ + static InetAddress makeIpv66plane(uint64_t nwid,uint64_t zeroTierAddress); +}; + +} // namespace ZeroTier + +#endif diff --git a/node/MAC.hpp b/node/MAC.hpp new file mode 100644 index 0000000..95623f1 --- /dev/null +++ b/node/MAC.hpp @@ -0,0 +1,264 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_MAC_HPP +#define ZT_MAC_HPP + +#include +#include +#include + +#include "Constants.hpp" +#include "Utils.hpp" +#include "Address.hpp" +#include "Buffer.hpp" + +namespace ZeroTier { + +/** + * 48-byte Ethernet MAC address + */ +class MAC +{ +public: + MAC() throw() : _m(0ULL) {} + MAC(const MAC &m) throw() : _m(m._m) {} + + MAC(const unsigned char a,const unsigned char b,const unsigned char c,const unsigned char d,const unsigned char e,const unsigned char f) throw() : + _m( ((((uint64_t)a) & 0xffULL) << 40) | + ((((uint64_t)b) & 0xffULL) << 32) | + ((((uint64_t)c) & 0xffULL) << 24) | + ((((uint64_t)d) & 0xffULL) << 16) | + ((((uint64_t)e) & 0xffULL) << 8) | + (((uint64_t)f) & 0xffULL) ) {} + + MAC(const char *s) throw() { fromString(s); } + MAC(const std::string &s) throw() { fromString(s.c_str()); } + + MAC(const void *bits,unsigned int len) throw() { setTo(bits,len); } + + MAC(const Address &ztaddr,uint64_t nwid) throw() { fromAddress(ztaddr,nwid); } + + MAC(const uint64_t m) throw() : _m(m & 0xffffffffffffULL) {} + + /** + * @return MAC in 64-bit integer + */ + inline uint64_t toInt() const throw() { return _m; } + + /** + * Set MAC to zero + */ + inline void zero() { _m = 0ULL; } + + /** + * @return True if MAC is non-zero + */ + inline operator bool() const throw() { return (_m != 0ULL); } + + /** + * @param bits Raw MAC in big-endian byte order + * @param len Length, must be >= 6 or result is zero + */ + inline void setTo(const void *bits,unsigned int len) + throw() + { + if (len < 6) { + _m = 0ULL; + return; + } + const unsigned char *b = (const unsigned char *)bits; + _m = ((((uint64_t)*b) & 0xff) << 40); ++b; + _m |= ((((uint64_t)*b) & 0xff) << 32); ++b; + _m |= ((((uint64_t)*b) & 0xff) << 24); ++b; + _m |= ((((uint64_t)*b) & 0xff) << 16); ++b; + _m |= ((((uint64_t)*b) & 0xff) << 8); ++b; + _m |= (((uint64_t)*b) & 0xff); + } + + /** + * @param buf Destination buffer for MAC in big-endian byte order + * @param len Length of buffer, must be >= 6 or nothing is copied + */ + inline void copyTo(void *buf,unsigned int len) const + throw() + { + if (len < 6) + return; + unsigned char *b = (unsigned char *)buf; + *(b++) = (unsigned char)((_m >> 40) & 0xff); + *(b++) = (unsigned char)((_m >> 32) & 0xff); + *(b++) = (unsigned char)((_m >> 24) & 0xff); + *(b++) = (unsigned char)((_m >> 16) & 0xff); + *(b++) = (unsigned char)((_m >> 8) & 0xff); + *b = (unsigned char)(_m & 0xff); + } + + /** + * Append to a buffer in big-endian byte order + * + * @param b Buffer to append to + */ + template + inline void appendTo(Buffer &b) const + throw(std::out_of_range) + { + unsigned char *p = (unsigned char *)b.appendField(6); + *(p++) = (unsigned char)((_m >> 40) & 0xff); + *(p++) = (unsigned char)((_m >> 32) & 0xff); + *(p++) = (unsigned char)((_m >> 24) & 0xff); + *(p++) = (unsigned char)((_m >> 16) & 0xff); + *(p++) = (unsigned char)((_m >> 8) & 0xff); + *p = (unsigned char)(_m & 0xff); + } + + /** + * @return True if this is broadcast (all 0xff) + */ + inline bool isBroadcast() const throw() { return (_m == 0xffffffffffffULL); } + + /** + * @return True if this is a multicast MAC + */ + inline bool isMulticast() const throw() { return ((_m & 0x010000000000ULL) != 0ULL); } + + /** + * @param True if this is a locally-administered MAC + */ + inline bool isLocallyAdministered() const throw() { return ((_m & 0x020000000000ULL) != 0ULL); } + + /** + * @param s Hex MAC, with or without : delimiters + */ + inline void fromString(const char *s) + { + char tmp[8]; + for(int i=0;i<6;++i) + tmp[i] = (char)0; + Utils::unhex(s,tmp,6); + setTo(tmp,6); + } + + /** + * @return MAC address in standard :-delimited hex format + */ + inline std::string toString() const + { + char tmp[24]; + toString(tmp,sizeof(tmp)); + return std::string(tmp); + } + + /** + * @param buf Buffer to contain human-readable MAC + * @param len Length of buffer + */ + inline void toString(char *buf,unsigned int len) const + { + Utils::snprintf(buf,len,"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)(*this)[0],(int)(*this)[1],(int)(*this)[2],(int)(*this)[3],(int)(*this)[4],(int)(*this)[5]); + } + + /** + * Set this MAC to a MAC derived from an address and a network ID + * + * @param ztaddr ZeroTier address + * @param nwid 64-bit network ID + */ + inline void fromAddress(const Address &ztaddr,uint64_t nwid) + throw() + { + uint64_t m = ((uint64_t)firstOctetForNetwork(nwid)) << 40; + m |= ztaddr.toInt(); // a is 40 bits + m ^= ((nwid >> 8) & 0xff) << 32; + m ^= ((nwid >> 16) & 0xff) << 24; + m ^= ((nwid >> 24) & 0xff) << 16; + m ^= ((nwid >> 32) & 0xff) << 8; + m ^= (nwid >> 40) & 0xff; + _m = m; + } + + /** + * Get the ZeroTier address for this MAC on this network (assuming no bridging of course, basic unicast) + * + * This just XORs the next-lest-significant 5 bytes of the network ID again to unmask. + * + * @param nwid Network ID + */ + inline Address toAddress(uint64_t nwid) const + throw() + { + uint64_t a = _m & 0xffffffffffULL; // least significant 40 bits of MAC are formed from address + a ^= ((nwid >> 8) & 0xff) << 32; // ... XORed with bits 8-48 of the nwid in little-endian byte order, so unmask it + a ^= ((nwid >> 16) & 0xff) << 24; + a ^= ((nwid >> 24) & 0xff) << 16; + a ^= ((nwid >> 32) & 0xff) << 8; + a ^= (nwid >> 40) & 0xff; + return Address(a); + } + + /** + * @param nwid Network ID + * @return First octet of MAC for this network + */ + static inline unsigned char firstOctetForNetwork(uint64_t nwid) + throw() + { + unsigned char a = ((unsigned char)(nwid & 0xfe) | 0x02); // locally administered, not multicast, from LSB of network ID + return ((a == 0x52) ? 0x32 : a); // blacklist 0x52 since it's used by KVM, libvirt, and other popular virtualization engines... seems de-facto standard on Linux + } + + /** + * @param i Value from 0 to 5 (inclusive) + * @return Byte at said position (address interpreted in big-endian order) + */ + inline unsigned char operator[](unsigned int i) const throw() { return (unsigned char)((_m >> (40 - (i * 8))) & 0xff); } + + /** + * @return 6, which is the number of bytes in a MAC, for container compliance + */ + inline unsigned int size() const throw() { return 6; } + + inline unsigned long hashCode() const throw() { return (unsigned long)_m; } + + inline MAC &operator=(const MAC &m) + throw() + { + _m = m._m; + return *this; + } + inline MAC &operator=(const uint64_t m) + throw() + { + _m = m; + return *this; + } + + inline bool operator==(const MAC &m) const throw() { return (_m == m._m); } + inline bool operator!=(const MAC &m) const throw() { return (_m != m._m); } + inline bool operator<(const MAC &m) const throw() { return (_m < m._m); } + inline bool operator<=(const MAC &m) const throw() { return (_m <= m._m); } + inline bool operator>(const MAC &m) const throw() { return (_m > m._m); } + inline bool operator>=(const MAC &m) const throw() { return (_m >= m._m); } + +private: + uint64_t _m; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Membership.cpp b/node/Membership.cpp new file mode 100644 index 0000000..2d0471f --- /dev/null +++ b/node/Membership.cpp @@ -0,0 +1,231 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include + +#include "Membership.hpp" +#include "RuntimeEnvironment.hpp" +#include "Peer.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Packet.hpp" +#include "Node.hpp" + +#define ZT_CREDENTIAL_PUSH_EVERY (ZT_NETWORK_AUTOCONF_DELAY / 3) + +namespace ZeroTier { + +Membership::Membership() : + _lastUpdatedMulticast(0), + _lastPushedCom(0), + _comRevocationThreshold(0), + _revocations(4), + _remoteTags(4), + _remoteCaps(4), + _remoteCoos(4) +{ + resetPushState(); +} + +void Membership::pushCredentials(const RuntimeEnvironment *RR,void *tPtr,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,int localCapabilityIndex,const bool force) +{ + bool sendCom = ( (nconf.com) && ( ((now - _lastPushedCom) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) ); + + const Capability *sendCap; + if (localCapabilityIndex >= 0) { + sendCap = &(nconf.capabilities[localCapabilityIndex]); + if ( ((now - _localCredLastPushed.cap[localCapabilityIndex]) >= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) + _localCredLastPushed.cap[localCapabilityIndex] = now; + else sendCap = (const Capability *)0; + } else sendCap = (const Capability *)0; + + const Tag *sendTags[ZT_MAX_NETWORK_TAGS]; + unsigned int sendTagCount = 0; + for(unsigned int t=0;t= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) { + _localCredLastPushed.tag[t] = now; + sendTags[sendTagCount++] = &(nconf.tags[t]); + } + } + + const CertificateOfOwnership *sendCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]; + unsigned int sendCooCount = 0; + for(unsigned int c=0;c= ZT_CREDENTIAL_PUSH_EVERY) || (force) ) { + _localCredLastPushed.coo[c] = now; + sendCoos[sendCooCount++] = &(nconf.certificatesOfOwnership[c]); + } + } + + unsigned int tagPtr = 0; + unsigned int cooPtr = 0; + while ((tagPtr < sendTagCount)||(cooPtr < sendCooCount)||(sendCom)||(sendCap)) { + Packet outp(peerAddress,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS); + + if (sendCom) { + sendCom = false; + nconf.com.serialize(outp); + _lastPushedCom = now; + } + outp.append((uint8_t)0x00); + + if (sendCap) { + outp.append((uint16_t)1); + sendCap->serialize(outp); + sendCap = (const Capability *)0; + } else outp.append((uint16_t)0); + + const unsigned int tagCountAt = outp.size(); + outp.addSize(2); + unsigned int thisPacketTagCount = 0; + while ((tagPtr < sendTagCount)&&((outp.size() + sizeof(Tag) + 16) < ZT_PROTO_MAX_PACKET_LENGTH)) { + sendTags[tagPtr++]->serialize(outp); + ++thisPacketTagCount; + } + outp.setAt(tagCountAt,(uint16_t)thisPacketTagCount); + + // No revocations, these propagate differently + outp.append((uint16_t)0); + + const unsigned int cooCountAt = outp.size(); + outp.addSize(2); + unsigned int thisPacketCooCount = 0; + while ((cooPtr < sendCooCount)&&((outp.size() + sizeof(CertificateOfOwnership) + 16) < ZT_PROTO_MAX_PACKET_LENGTH)) { + sendCoos[cooPtr++]->serialize(outp); + ++thisPacketCooCount; + } + outp.setAt(cooCountAt,(uint16_t)thisPacketCooCount); + + outp.compress(); + RR->sw->send(tPtr,outp,true); + } +} + +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const CertificateOfMembership &com) +{ + const uint64_t newts = com.timestamp(); + if (newts <= _comRevocationThreshold) { + TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (revoked)",com.issuedTo().toString().c_str(),com.networkId()); + return ADD_REJECTED; + } + + const uint64_t oldts = _com.timestamp(); + if (newts < oldts) { + TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (older than current)",com.issuedTo().toString().c_str(),com.networkId()); + return ADD_REJECTED; + } + if ((newts == oldts)&&(_com == com)) { + TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (redundant)",com.issuedTo().toString().c_str(),com.networkId()); + return ADD_ACCEPTED_REDUNDANT; + } + + switch(com.verify(RR,tPtr)) { + default: + TRACE("addCredential(CertificateOfMembership) for %s on %.16llx REJECTED (invalid signature or object)",com.issuedTo().toString().c_str(),com.networkId()); + return ADD_REJECTED; + case 0: + TRACE("addCredential(CertificateOfMembership) for %s on %.16llx ACCEPTED (new)",com.issuedTo().toString().c_str(),com.networkId()); + _com = com; + return ADD_ACCEPTED_NEW; + case 1: + return ADD_DEFERRED_FOR_WHOIS; + } +} + +// Template out addCredential() for many cred types to avoid copypasta +template +static Membership::AddCredentialResult _addCredImpl(Hashtable &remoteCreds,const Hashtable &revocations,const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const C &cred) +{ + C *rc = remoteCreds.get(cred.id()); + if (rc) { + if (rc->timestamp() > cred.timestamp()) { + TRACE("addCredential(type==%d) for %s on %.16llx REJECTED (older than credential we have)",(int)C::credentialType(),cred.issuedTo().toString().c_str(),cred.networkId()); + return Membership::ADD_REJECTED; + } + if (*rc == cred) { + //TRACE("addCredential(type==%d) for %s on %.16llx ACCEPTED (redundant)",(int)C::credentialType(),cred.issuedTo().toString().c_str(),cred.networkId()); + return Membership::ADD_ACCEPTED_REDUNDANT; + } + } + + const uint64_t *const rt = revocations.get(Membership::credentialKey(C::credentialType(),cred.id())); + if ((rt)&&(*rt >= cred.timestamp())) { + TRACE("addCredential(type==%d) for %s on %.16llx REJECTED (timestamp below revocation threshold)",(int)C::credentialType(),cred.issuedTo().toString().c_str(),cred.networkId()); + return Membership::ADD_REJECTED; + } + + switch(cred.verify(RR,tPtr)) { + default: + TRACE("addCredential(type==%d) for %s on %.16llx REJECTED (invalid)",(int)C::credentialType(),cred.issuedTo().toString().c_str(),cred.networkId()); + return Membership::ADD_REJECTED; + case 0: + TRACE("addCredential(type==%d) for %s on %.16llx ACCEPTED (new)",(int)C::credentialType(),cred.issuedTo().toString().c_str(),cred.networkId()); + if (!rc) + rc = &(remoteCreds[cred.id()]); + *rc = cred; + return Membership::ADD_ACCEPTED_NEW; + case 1: + return Membership::ADD_DEFERRED_FOR_WHOIS; + } +} + +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Tag &tag) { return _addCredImpl(_remoteTags,_revocations,RR,tPtr,nconf,tag); } +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Capability &cap) { return _addCredImpl(_remoteCaps,_revocations,RR,tPtr,nconf,cap); } +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const CertificateOfOwnership &coo) { return _addCredImpl(_remoteCoos,_revocations,RR,tPtr,nconf,coo); } + +Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Revocation &rev) +{ + uint64_t *rt; + switch(rev.verify(RR,tPtr)) { + default: + return ADD_REJECTED; + case 0: { + const Credential::Type ct = rev.type(); + switch(ct) { + case Credential::CREDENTIAL_TYPE_COM: + if (rev.threshold() > _comRevocationThreshold) { + _comRevocationThreshold = rev.threshold(); + return ADD_ACCEPTED_NEW; + } + return ADD_ACCEPTED_REDUNDANT; + case Credential::CREDENTIAL_TYPE_CAPABILITY: + case Credential::CREDENTIAL_TYPE_TAG: + case Credential::CREDENTIAL_TYPE_COO: + rt = &(_revocations[credentialKey(ct,rev.credentialId())]); + if (*rt < rev.threshold()) { + *rt = rev.threshold(); + return ADD_ACCEPTED_NEW; + } + return ADD_ACCEPTED_REDUNDANT; + default: + return ADD_REJECTED; + } + } + case 1: + return ADD_DEFERRED_FOR_WHOIS; + } +} + +void Membership::clean(const uint64_t now,const NetworkConfig &nconf) +{ + _cleanCredImpl(nconf,_remoteTags); + _cleanCredImpl(nconf,_remoteCaps); + _cleanCredImpl(nconf,_remoteCoos); +} + +} // namespace ZeroTier diff --git a/node/Membership.hpp b/node/Membership.hpp new file mode 100644 index 0000000..0bc8f33 --- /dev/null +++ b/node/Membership.hpp @@ -0,0 +1,272 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_MEMBERSHIP_HPP +#define ZT_MEMBERSHIP_HPP + +#include + +#include "Constants.hpp" +#include "../include/ZeroTierOne.h" +#include "Credential.hpp" +#include "Hashtable.hpp" +#include "CertificateOfMembership.hpp" +#include "Capability.hpp" +#include "Tag.hpp" +#include "Revocation.hpp" +#include "NetworkConfig.hpp" + +#define ZT_MEMBERSHIP_CRED_ID_UNUSED 0xffffffffffffffffULL + +namespace ZeroTier { + +class RuntimeEnvironment; +class Network; + +/** + * A container for certificates of membership and other network credentials + * + * This is essentially a relational join between Peer and Network. + * + * This class is not thread safe. It must be locked externally. + */ +class Membership +{ +public: + enum AddCredentialResult + { + ADD_REJECTED, + ADD_ACCEPTED_NEW, + ADD_ACCEPTED_REDUNDANT, + ADD_DEFERRED_FOR_WHOIS + }; + + Membership(); + + /** + * Send COM and other credentials to this peer if needed + * + * This checks last pushed times for our COM and for other credentials and + * sends VERB_NETWORK_CREDENTIALS if the recipient might need them. + * + * @param RR Runtime environment + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param now Current time + * @param peerAddress Address of member peer (the one that this Membership describes) + * @param nconf My network config + * @param localCapabilityIndex Index of local capability to include (in nconf.capabilities[]) or -1 if none + * @param force If true, send objects regardless of last push time + */ + void pushCredentials(const RuntimeEnvironment *RR,void *tPtr,const uint64_t now,const Address &peerAddress,const NetworkConfig &nconf,int localCapabilityIndex,const bool force); + + /** + * Check whether we should push MULTICAST_LIKEs to this peer, and update last sent time if true + * + * @param now Current time + * @return True if we should update multicasts + */ + inline bool multicastLikeGate(const uint64_t now) + { + if ((now - _lastUpdatedMulticast) >= ZT_MULTICAST_ANNOUNCE_PERIOD) { + _lastUpdatedMulticast = now; + return true; + } + return false; + } + + /** + * Check whether the peer represented by this Membership should be allowed on this network at all + * + * @param nconf Our network config + * @return True if this peer is allowed on this network at all + */ + inline bool isAllowedOnNetwork(const NetworkConfig &nconf) const + { + if (nconf.isPublic()) return true; + if (_com.timestamp() <= _comRevocationThreshold) return false; + return nconf.com.agreesWith(_com); + } + + /** + * Check whether the peer represented by this Membership owns a given resource + * + * @tparam Type of resource: InetAddress or MAC + * @param nconf Our network config + * @param r Resource to check + * @return True if this peer has a certificate of ownership for the given resource + */ + template + inline bool hasCertificateOfOwnershipFor(const NetworkConfig &nconf,const T &r) const + { + uint32_t *k = (uint32_t *)0; + CertificateOfOwnership *v = (CertificateOfOwnership *)0; + Hashtable< uint32_t,CertificateOfOwnership >::Iterator i(*(const_cast< Hashtable< uint32_t,CertificateOfOwnership> *>(&_remoteCoos))); + while (i.next(k,v)) { + if (_isCredentialTimestampValid(nconf,*v)&&(v->owns(r))) + return true; + } + return false; + } + + /** + * Get a remote member's tag (if we have it) + * + * @param nconf Network configuration + * @param id Tag ID + * @return Pointer to tag or NULL if not found + */ + inline const Tag *getTag(const NetworkConfig &nconf,const uint32_t id) const + { + const Tag *const t = _remoteTags.get(id); + return (((t)&&(_isCredentialTimestampValid(nconf,*t))) ? t : (Tag *)0); + } + + /** + * Validate and add a credential if signature is okay and it's otherwise good + */ + AddCredentialResult addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const CertificateOfMembership &com); + + /** + * Validate and add a credential if signature is okay and it's otherwise good + */ + AddCredentialResult addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Tag &tag); + + /** + * Validate and add a credential if signature is okay and it's otherwise good + */ + AddCredentialResult addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Capability &cap); + + /** + * Validate and add a credential if signature is okay and it's otherwise good + */ + AddCredentialResult addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const CertificateOfOwnership &coo); + + /** + * Validate and add a credential if signature is okay and it's otherwise good + */ + AddCredentialResult addCredential(const RuntimeEnvironment *RR,void *tPtr,const NetworkConfig &nconf,const Revocation &rev); + + /** + * Clean internal databases of stale entries + * + * @param now Current time + * @param nconf Current network configuration + */ + void clean(const uint64_t now,const NetworkConfig &nconf); + + /** + * Reset last pushed time for local credentials + * + * This is done when we update our network configuration and our credentials have changed + */ + inline void resetPushState() + { + _lastPushedCom = 0; + memset(&_localCredLastPushed,0,sizeof(_localCredLastPushed)); + } + + /** + * Generates a key for the internal use in indexing credentials by type and credential ID + */ + static uint64_t credentialKey(const Credential::Type &t,const uint32_t i) { return (((uint64_t)t << 32) | (uint64_t)i); } + +private: + template + inline bool _isCredentialTimestampValid(const NetworkConfig &nconf,const C &remoteCredential) const + { + const uint64_t ts = remoteCredential.timestamp(); + if (((ts >= nconf.timestamp) ? (ts - nconf.timestamp) : (nconf.timestamp - ts)) <= nconf.credentialTimeMaxDelta) { + const uint64_t *threshold = _revocations.get(credentialKey(C::credentialType(),remoteCredential.id())); + return ((!threshold)||(ts > *threshold)); + } + return false; + } + + template + void _cleanCredImpl(const NetworkConfig &nconf,Hashtable &remoteCreds) + { + uint32_t *k = (uint32_t *)0; + C *v = (C *)0; + typename Hashtable::Iterator i(remoteCreds); + while (i.next(k,v)) { + if (!_isCredentialTimestampValid(nconf,*v)) + remoteCreds.erase(*k); + } + } + + // Last time we pushed MULTICAST_LIKE(s) + uint64_t _lastUpdatedMulticast; + + // Last time we pushed our COM to this peer + uint64_t _lastPushedCom; + + // Revocation threshold for COM or 0 if none + uint64_t _comRevocationThreshold; + + // Remote member's latest network COM + CertificateOfMembership _com; + + // Revocations by credentialKey() + Hashtable< uint64_t,uint64_t > _revocations; + + // Remote credentials that we have received from this member (and that are valid) + Hashtable< uint32_t,Tag > _remoteTags; + Hashtable< uint32_t,Capability > _remoteCaps; + Hashtable< uint32_t,CertificateOfOwnership > _remoteCoos; + + // Time we last pushed our local credentials to this member + struct { + uint64_t tag[ZT_MAX_NETWORK_TAGS]; + uint64_t cap[ZT_MAX_NETWORK_CAPABILITIES]; + uint64_t coo[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]; + } _localCredLastPushed; + +public: + class CapabilityIterator + { + public: + CapabilityIterator(Membership &m,const NetworkConfig &nconf) : + _hti(m._remoteCaps), + _k((uint32_t *)0), + _c((Capability *)0), + _m(m), + _nconf(nconf) + { + } + + inline Capability *next() + { + while (_hti.next(_k,_c)) { + if (_m._isCredentialTimestampValid(_nconf,*_c)) + return _c; + } + return (Capability *)0; + } + + private: + Hashtable< uint32_t,Capability >::Iterator _hti; + uint32_t *_k; + Capability *_c; + Membership &_m; + const NetworkConfig &_nconf; + }; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/MulticastGroup.hpp b/node/MulticastGroup.hpp new file mode 100644 index 0000000..be4e808 --- /dev/null +++ b/node/MulticastGroup.hpp @@ -0,0 +1,132 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_MULTICASTGROUP_HPP +#define ZT_MULTICASTGROUP_HPP + +#include + +#include + +#include "MAC.hpp" +#include "InetAddress.hpp" + +namespace ZeroTier { + +/** + * A multicast group composed of a multicast MAC and a 32-bit ADI field + * + * ADI stands for additional distinguishing information. ADI is primarily for + * adding additional information to broadcast (ff:ff:ff:ff:ff:ff) memberships, + * since straight-up broadcast won't scale. Right now it's zero except for + * IPv4 ARP, where it holds the IPv4 address itself to make ARP into a + * selective multicast query that can scale. + * + * In the future we might add some kind of plugin architecture that can add + * ADI for things like mDNS (multicast DNS) to improve the selectivity of + * those protocols. + * + * MulticastGroup behaves as an immutable value object. + */ +class MulticastGroup +{ +public: + MulticastGroup() + throw() : + _mac(), + _adi(0) + { + } + + MulticastGroup(const MAC &m,uint32_t a) + throw() : + _mac(m), + _adi(a) + { + } + + /** + * Derive the multicast group used for address resolution (ARP/NDP) for an IP + * + * @param ip IP address (port field is ignored) + * @return Multicat group for ARP/NDP + */ + static inline MulticastGroup deriveMulticastGroupForAddressResolution(const InetAddress &ip) + throw() + { + if (ip.isV4()) { + // IPv4 wants broadcast MACs, so we shove the V4 address itself into + // the Multicast Group ADI field. Making V4 ARP work is basically why + // ADI was added, as well as handling other things that want mindless + // Ethernet broadcast to all. + return MulticastGroup(MAC(0xffffffffffffULL),Utils::ntoh(*((const uint32_t *)ip.rawIpData()))); + } else if (ip.isV6()) { + // IPv6 is better designed in this respect. We can compute the IPv6 + // multicast address directly from the IP address, and it gives us + // 24 bits of uniqueness. Collisions aren't likely to be common enough + // to care about. + const unsigned char *a = (const unsigned char *)ip.rawIpData(); + return MulticastGroup(MAC(0x33,0x33,0xff,a[13],a[14],a[15]),0); + } + return MulticastGroup(); + } + + /** + * @return Human readable string representing this group (MAC/ADI in hex) + */ + inline std::string toString() const + { + char buf[64]; + Utils::snprintf(buf,sizeof(buf),"%.2x%.2x%.2x%.2x%.2x%.2x/%.8lx",(unsigned int)_mac[0],(unsigned int)_mac[1],(unsigned int)_mac[2],(unsigned int)_mac[3],(unsigned int)_mac[4],(unsigned int)_mac[5],(unsigned long)_adi); + return std::string(buf); + } + + /** + * @return Multicast address + */ + inline const MAC &mac() const throw() { return _mac; } + + /** + * @return Additional distinguishing information + */ + inline uint32_t adi() const throw() { return _adi; } + + inline unsigned long hashCode() const throw() { return (_mac.hashCode() ^ (unsigned long)_adi); } + + inline bool operator==(const MulticastGroup &g) const throw() { return ((_mac == g._mac)&&(_adi == g._adi)); } + inline bool operator!=(const MulticastGroup &g) const throw() { return ((_mac != g._mac)||(_adi != g._adi)); } + inline bool operator<(const MulticastGroup &g) const throw() + { + if (_mac < g._mac) + return true; + else if (_mac == g._mac) + return (_adi < g._adi); + return false; + } + inline bool operator>(const MulticastGroup &g) const throw() { return (g < *this); } + inline bool operator<=(const MulticastGroup &g) const throw() { return !(g < *this); } + inline bool operator>=(const MulticastGroup &g) const throw() { return !(*this < g); } + +private: + MAC _mac; + uint32_t _adi; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp new file mode 100644 index 0000000..8e534b5 --- /dev/null +++ b/node/Multicaster.cpp @@ -0,0 +1,395 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include + +#include "Constants.hpp" +#include "RuntimeEnvironment.hpp" +#include "SharedPtr.hpp" +#include "Multicaster.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Packet.hpp" +#include "Peer.hpp" +#include "C25519.hpp" +#include "CertificateOfMembership.hpp" +#include "Node.hpp" + +namespace ZeroTier { + +Multicaster::Multicaster(const RuntimeEnvironment *renv) : + RR(renv), + _groups(256), + _gatherAuth(256) +{ +} + +Multicaster::~Multicaster() +{ +} + +void Multicaster::addMultiple(void *tPtr,uint64_t now,uint64_t nwid,const MulticastGroup &mg,const void *addresses,unsigned int count,unsigned int totalKnown) +{ + const unsigned char *p = (const unsigned char *)addresses; + const unsigned char *e = p + (5 * count); + Mutex::Lock _l(_groups_m); + MulticastGroupStatus &gs = _groups[Multicaster::Key(nwid,mg)]; + while (p != e) { + _add(tPtr,now,nwid,mg,gs,Address(p,5)); + p += 5; + } +} + +void Multicaster::remove(uint64_t nwid,const MulticastGroup &mg,const Address &member) +{ + Mutex::Lock _l(_groups_m); + MulticastGroupStatus *s = _groups.get(Multicaster::Key(nwid,mg)); + if (s) { + for(std::vector::iterator m(s->members.begin());m!=s->members.end();++m) { + if (m->address == member) { + s->members.erase(m); + break; + } + } + } +} + +unsigned int Multicaster::gather(const Address &queryingPeer,uint64_t nwid,const MulticastGroup &mg,Buffer &appendTo,unsigned int limit) const +{ + unsigned char *p; + unsigned int added = 0,i,k,rptr,totalKnown = 0; + uint64_t a,picked[(ZT_PROTO_MAX_PACKET_LENGTH / 5) + 2]; + + if (!limit) + return 0; + else if (limit > 0xffff) + limit = 0xffff; + + const unsigned int totalAt = appendTo.size(); + appendTo.addSize(4); // sizeof(uint32_t) + const unsigned int addedAt = appendTo.size(); + appendTo.addSize(2); // sizeof(uint16_t) + + { // Return myself if I am a member of this group + SharedPtr network(RR->node->network(nwid)); + if ((network)&&(network->subscribedToMulticastGroup(mg,true))) { + RR->identity.address().appendTo(appendTo); + ++totalKnown; + ++added; + } + } + + Mutex::Lock _l(_groups_m); + + const MulticastGroupStatus *s = _groups.get(Multicaster::Key(nwid,mg)); + if ((s)&&(!s->members.empty())) { + totalKnown += (unsigned int)s->members.size(); + + // Members are returned in random order so that repeated gather queries + // will return different subsets of a large multicast group. + k = 0; + while ((added < limit)&&(k < s->members.size())&&((appendTo.size() + ZT_ADDRESS_LENGTH) <= ZT_UDP_DEFAULT_PAYLOAD_MTU)) { + rptr = (unsigned int)RR->node->prng(); + +restart_member_scan: + a = s->members[rptr % (unsigned int)s->members.size()].address.toInt(); + for(i=0;i> 32) & 0xff); + *(p++) = (unsigned char)((a >> 24) & 0xff); + *(p++) = (unsigned char)((a >> 16) & 0xff); + *(p++) = (unsigned char)((a >> 8) & 0xff); + *p = (unsigned char)(a & 0xff); + ++added; + } + } + } + + appendTo.setAt(totalAt,(uint32_t)totalKnown); + appendTo.setAt(addedAt,(uint16_t)added); + + //TRACE("..MC Multicaster::gather() attached %u of %u peers for %.16llx/%s (2)",n,(unsigned int)(gs->second.members.size() - skipped),nwid,mg.toString().c_str()); + + return added; +} + +std::vector
Multicaster::getMembers(uint64_t nwid,const MulticastGroup &mg,unsigned int limit) const +{ + std::vector
ls; + Mutex::Lock _l(_groups_m); + const MulticastGroupStatus *s = _groups.get(Multicaster::Key(nwid,mg)); + if (!s) + return ls; + for(std::vector::const_reverse_iterator m(s->members.rbegin());m!=s->members.rend();++m) { + ls.push_back(m->address); + if (ls.size() >= limit) + break; + } + return ls; +} + +void Multicaster::send( + void *tPtr, + unsigned int limit, + uint64_t now, + uint64_t nwid, + bool disableCompression, + const std::vector
&alwaysSendTo, + const MulticastGroup &mg, + const MAC &src, + unsigned int etherType, + const void *data, + unsigned int len) +{ + unsigned long idxbuf[8194]; + unsigned long *indexes = idxbuf; + + try { + Mutex::Lock _l(_groups_m); + MulticastGroupStatus &gs = _groups[Multicaster::Key(nwid,mg)]; + + if (!gs.members.empty()) { + // Allocate a memory buffer if group is monstrous + if (gs.members.size() > (sizeof(idxbuf) / sizeof(unsigned long))) + indexes = new unsigned long[gs.members.size()]; + + // Generate a random permutation of member indexes + for(unsigned long i=0;i0;--i) { + unsigned long j = (unsigned long)RR->node->prng() % (i + 1); + unsigned long tmp = indexes[j]; + indexes[j] = indexes[i]; + indexes[i] = tmp; + } + } + + if (gs.members.size() >= limit) { + // Skip queue if we already have enough members to complete the send operation + OutboundMulticast out; + + out.init( + RR, + now, + nwid, + disableCompression, + limit, + 1, // we'll still gather a little from peers to keep multicast list fresh + src, + mg, + etherType, + data, + len); + + unsigned int count = 0; + + for(std::vector
::const_iterator ast(alwaysSendTo.begin());ast!=alwaysSendTo.end();++ast) { + if (*ast != RR->identity.address()) { + out.sendOnly(RR,tPtr,*ast); // optimization: don't use dedup log if it's a one-pass send + if (++count >= limit) + break; + } + } + + unsigned long idx = 0; + while ((count < limit)&&(idx < gs.members.size())) { + Address ma(gs.members[indexes[idx++]].address); + if (std::find(alwaysSendTo.begin(),alwaysSendTo.end(),ma) == alwaysSendTo.end()) { + out.sendOnly(RR,tPtr,ma); // optimization: don't use dedup log if it's a one-pass send + ++count; + } + } + } else { + unsigned int gatherLimit = (limit - (unsigned int)gs.members.size()) + 1; + + if ((gs.members.empty())||((now - gs.lastExplicitGather) >= ZT_MULTICAST_EXPLICIT_GATHER_DELAY)) { + gs.lastExplicitGather = now; + + Address explicitGatherPeers[16]; + unsigned int numExplicitGatherPeers = 0; + SharedPtr bestRoot(RR->topology->getUpstreamPeer()); + if (bestRoot) + explicitGatherPeers[numExplicitGatherPeers++] = bestRoot->address(); + explicitGatherPeers[numExplicitGatherPeers++] = Network::controllerFor(nwid); + SharedPtr network(RR->node->network(nwid)); + if (network) { + std::vector
anchors(network->config().anchors()); + for(std::vector
::const_iterator a(anchors.begin());a!=anchors.end();++a) { + if (*a != RR->identity.address()) { + explicitGatherPeers[numExplicitGatherPeers++] = *a; + if (numExplicitGatherPeers == 16) + break; + } + } + } + + for(unsigned int k=0;kconfig().com) ? &(network->config().com) : (const CertificateOfMembership *)0) : (const CertificateOfMembership *)0; + Packet outp(explicitGatherPeers[k],RR->identity.address(),Packet::VERB_MULTICAST_GATHER); + outp.append(nwid); + outp.append((uint8_t)((com) ? 0x01 : 0x00)); + mg.mac().appendTo(outp); + outp.append((uint32_t)mg.adi()); + outp.append((uint32_t)gatherLimit); + if (com) + com->serialize(outp); + RR->node->expectReplyTo(outp.packetId()); + RR->sw->send(tPtr,outp,true); + } + } + + gs.txQueue.push_back(OutboundMulticast()); + OutboundMulticast &out = gs.txQueue.back(); + + out.init( + RR, + now, + nwid, + disableCompression, + limit, + gatherLimit, + src, + mg, + etherType, + data, + len); + + unsigned int count = 0; + + for(std::vector
::const_iterator ast(alwaysSendTo.begin());ast!=alwaysSendTo.end();++ast) { + if (*ast != RR->identity.address()) { + out.sendAndLog(RR,tPtr,*ast); + if (++count >= limit) + break; + } + } + + unsigned long idx = 0; + while ((count < limit)&&(idx < gs.members.size())) { + Address ma(gs.members[indexes[idx++]].address); + if (std::find(alwaysSendTo.begin(),alwaysSendTo.end(),ma) == alwaysSendTo.end()) { + out.sendAndLog(RR,tPtr,ma); + ++count; + } + } + } + } catch ( ... ) {} // this is a sanity check to catch any failures and make sure indexes[] still gets deleted + + // Free allocated memory buffer if any + if (indexes != idxbuf) + delete [] indexes; +} + +void Multicaster::clean(uint64_t now) +{ + { + Mutex::Lock _l(_groups_m); + Multicaster::Key *k = (Multicaster::Key *)0; + MulticastGroupStatus *s = (MulticastGroupStatus *)0; + Hashtable::Iterator mm(_groups); + while (mm.next(k,s)) { + for(std::list::iterator tx(s->txQueue.begin());tx!=s->txQueue.end();) { + if ((tx->expired(now))||(tx->atLimit())) + s->txQueue.erase(tx++); + else ++tx; + } + + unsigned long count = 0; + { + std::vector::iterator reader(s->members.begin()); + std::vector::iterator writer(reader); + while (reader != s->members.end()) { + if ((now - reader->timestamp) < ZT_MULTICAST_LIKE_EXPIRE) { + *writer = *reader; + ++writer; + ++count; + } + ++reader; + } + } + + if (count) { + s->members.resize(count); + } else if (s->txQueue.empty()) { + _groups.erase(*k); + } else { + s->members.clear(); + } + } + } + + { + Mutex::Lock _l(_gatherAuth_m); + _GatherAuthKey *k = (_GatherAuthKey *)0; + uint64_t *ts = NULL; + Hashtable<_GatherAuthKey,uint64_t>::Iterator i(_gatherAuth); + while (i.next(k,ts)) { + if ((now - *ts) >= ZT_MULTICAST_CREDENTIAL_EXPIRATON) + _gatherAuth.erase(*k); + } + } +} + +void Multicaster::addCredential(void *tPtr,const CertificateOfMembership &com,bool alreadyValidated) +{ + if ((alreadyValidated)||(com.verify(RR,tPtr) == 0)) { + Mutex::Lock _l(_gatherAuth_m); + _gatherAuth[_GatherAuthKey(com.networkId(),com.issuedTo())] = RR->node->now(); + } +} + +void Multicaster::_add(void *tPtr,uint64_t now,uint64_t nwid,const MulticastGroup &mg,MulticastGroupStatus &gs,const Address &member) +{ + // assumes _groups_m is locked + + // Do not add self -- even if someone else returns it + if (member == RR->identity.address()) + return; + + for(std::vector::iterator m(gs.members.begin());m!=gs.members.end();++m) { + if (m->address == member) { + m->timestamp = now; + return; + } + } + + gs.members.push_back(MulticastGroupMember(member,now)); + + //TRACE("..MC %s joined multicast group %.16llx/%s via %s",member.toString().c_str(),nwid,mg.toString().c_str(),((learnedFrom) ? learnedFrom.toString().c_str() : "(direct)")); + + for(std::list::iterator tx(gs.txQueue.begin());tx!=gs.txQueue.end();) { + if (tx->atLimit()) + gs.txQueue.erase(tx++); + else { + tx->sendIfNew(RR,tPtr,member); + if (tx->atLimit()) + gs.txQueue.erase(tx++); + else ++tx; + } + } +} + +} // namespace ZeroTier diff --git a/node/Multicaster.hpp b/node/Multicaster.hpp new file mode 100644 index 0000000..f646a5b --- /dev/null +++ b/node/Multicaster.hpp @@ -0,0 +1,237 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_MULTICASTER_HPP +#define ZT_MULTICASTER_HPP + +#include +#include + +#include +#include +#include + +#include "Constants.hpp" +#include "Hashtable.hpp" +#include "Address.hpp" +#include "MAC.hpp" +#include "MulticastGroup.hpp" +#include "OutboundMulticast.hpp" +#include "Utils.hpp" +#include "Mutex.hpp" +#include "NonCopyable.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; +class CertificateOfMembership; +class Packet; + +/** + * Database of known multicast peers within a network + */ +class Multicaster : NonCopyable +{ +private: + struct Key + { + Key() : nwid(0),mg() {} + Key(uint64_t n,const MulticastGroup &g) : nwid(n),mg(g) {} + + uint64_t nwid; + MulticastGroup mg; + + inline bool operator==(const Key &k) const throw() { return ((nwid == k.nwid)&&(mg == k.mg)); } + inline unsigned long hashCode() const throw() { return (mg.hashCode() ^ (unsigned long)(nwid ^ (nwid >> 32))); } + }; + + struct MulticastGroupMember + { + MulticastGroupMember() {} + MulticastGroupMember(const Address &a,uint64_t ts) : address(a),timestamp(ts) {} + + Address address; + uint64_t timestamp; // time of last notification + }; + + struct MulticastGroupStatus + { + MulticastGroupStatus() : lastExplicitGather(0) {} + + uint64_t lastExplicitGather; + std::list txQueue; // pending outbound multicasts + std::vector members; // members of this group + }; + +public: + Multicaster(const RuntimeEnvironment *renv); + ~Multicaster(); + + /** + * Add or update a member in a multicast group + * + * @param now Current time + * @param nwid Network ID + * @param mg Multicast group + * @param member New member address + */ + inline void add(void *tPtr,uint64_t now,uint64_t nwid,const MulticastGroup &mg,const Address &member) + { + Mutex::Lock _l(_groups_m); + _add(tPtr,now,nwid,mg,_groups[Multicaster::Key(nwid,mg)],member); + } + + /** + * Add multiple addresses from a binary array of 5-byte address fields + * + * It's up to the caller to check bounds on the array before calling this. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param now Current time + * @param nwid Network ID + * @param mg Multicast group + * @param addresses Raw binary addresses in big-endian format, as a series of 5-byte fields + * @param count Number of addresses + * @param totalKnown Total number of known addresses as reported by peer + */ + void addMultiple(void *tPtr,uint64_t now,uint64_t nwid,const MulticastGroup &mg,const void *addresses,unsigned int count,unsigned int totalKnown); + + /** + * Remove a multicast group member (if present) + * + * @param nwid Network ID + * @param mg Multicast group + * @param member Member to unsubscribe + */ + void remove(uint64_t nwid,const MulticastGroup &mg,const Address &member); + + /** + * Append gather results to a packet by choosing registered multicast recipients at random + * + * This appends the following fields to the packet: + * <[4] 32-bit total number of known members in this multicast group> + * <[2] 16-bit number of members enumerated in this packet> + * <[...] series of 5-byte ZeroTier addresses of enumerated members> + * + * If zero is returned, the first two fields will still have been appended. + * + * @param queryingPeer Peer asking for gather (to skip in results) + * @param nwid Network ID + * @param mg Multicast group + * @param appendTo Packet to append to + * @param limit Maximum number of 5-byte addresses to append + * @return Number of addresses appended + * @throws std::out_of_range Buffer overflow writing to packet + */ + unsigned int gather(const Address &queryingPeer,uint64_t nwid,const MulticastGroup &mg,Buffer &appendTo,unsigned int limit) const; + + /** + * Get subscribers to a multicast group + * + * @param nwid Network ID + * @param mg Multicast group + */ + std::vector
getMembers(uint64_t nwid,const MulticastGroup &mg,unsigned int limit) const; + + /** + * Send a multicast + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param limit Multicast limit + * @param now Current time + * @param nwid Network ID + * @param disableCompression Disable packet payload compression? + * @param alwaysSendTo Send to these peers first and even if not included in subscriber list + * @param mg Multicast group + * @param src Source Ethernet MAC address or NULL to skip in packet and compute from ZT address (non-bridged mode) + * @param etherType Ethernet frame type + * @param data Packet data + * @param len Length of packet data + */ + void send( + void *tPtr, + unsigned int limit, + uint64_t now, + uint64_t nwid, + bool disableCompression, + const std::vector
&alwaysSendTo, + const MulticastGroup &mg, + const MAC &src, + unsigned int etherType, + const void *data, + unsigned int len); + + /** + * Clean up and resort database + * + * @param RR Runtime environment + * @param now Current time + */ + void clean(uint64_t now); + + /** + * Add an authorization credential + * + * The Multicaster keeps its own track of when valid credentials of network + * membership are presented. This allows it to control MULTICAST_LIKE + * GATHER authorization for networks this node does not belong to. + * + * @param com Certificate of membership + * @param alreadyValidated If true, COM has already been checked and found to be valid and signed + */ + void addCredential(void *tPtr,const CertificateOfMembership &com,bool alreadyValidated); + + /** + * Check authorization for GATHER and LIKE for non-network-members + * + * @param a Address of peer + * @param nwid Network ID + * @param now Current time + * @return True if GATHER and LIKE should be allowed + */ + bool cacheAuthorized(const Address &a,const uint64_t nwid,const uint64_t now) const + { + Mutex::Lock _l(_gatherAuth_m); + const uint64_t *p = _gatherAuth.get(_GatherAuthKey(nwid,a)); + return ((p)&&((now - *p) < ZT_MULTICAST_CREDENTIAL_EXPIRATON)); + } + +private: + void _add(void *tPtr,uint64_t now,uint64_t nwid,const MulticastGroup &mg,MulticastGroupStatus &gs,const Address &member); + + const RuntimeEnvironment *RR; + + Hashtable _groups; + Mutex _groups_m; + + struct _GatherAuthKey + { + _GatherAuthKey() : member(0),networkId(0) {} + _GatherAuthKey(const uint64_t nwid,const Address &a) : member(a.toInt()),networkId(nwid) {} + inline unsigned long hashCode() const { return (unsigned long)(member ^ networkId); } + inline bool operator==(const _GatherAuthKey &k) const { return ((member == k.member)&&(networkId == k.networkId)); } + uint64_t member; + uint64_t networkId; + }; + Hashtable< _GatherAuthKey,uint64_t > _gatherAuth; + Mutex _gatherAuth_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Mutex.hpp b/node/Mutex.hpp new file mode 100644 index 0000000..d451ede --- /dev/null +++ b/node/Mutex.hpp @@ -0,0 +1,186 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_MUTEX_HPP +#define ZT_MUTEX_HPP + +#include "Constants.hpp" +#include "NonCopyable.hpp" + +#ifdef __UNIX_LIKE__ + +#include +#include + +namespace ZeroTier { + +class Mutex : NonCopyable +{ +public: + Mutex() + throw() + { + pthread_mutex_init(&_mh,(const pthread_mutexattr_t *)0); + } + + ~Mutex() + { + pthread_mutex_destroy(&_mh); + } + + inline void lock() + throw() + { + pthread_mutex_lock(&_mh); + } + + inline void unlock() + throw() + { + pthread_mutex_unlock(&_mh); + } + + inline void lock() const + throw() + { + (const_cast (this))->lock(); + } + + inline void unlock() const + throw() + { + (const_cast (this))->unlock(); + } + + /** + * Uses C++ contexts and constructor/destructor to lock/unlock automatically + */ + class Lock : NonCopyable + { + public: + Lock(Mutex &m) + throw() : + _m(&m) + { + m.lock(); + } + + Lock(const Mutex &m) + throw() : + _m(const_cast(&m)) + { + _m->lock(); + } + + ~Lock() + { + _m->unlock(); + } + + private: + Mutex *const _m; + }; + +private: + pthread_mutex_t _mh; +}; + +} // namespace ZeroTier + +#endif // Apple / Linux + +#ifdef __WINDOWS__ + +#include +#include + +namespace ZeroTier { + +class Mutex : NonCopyable +{ +public: + Mutex() + throw() + { + InitializeCriticalSection(&_cs); + } + + ~Mutex() + { + DeleteCriticalSection(&_cs); + } + + inline void lock() + throw() + { + EnterCriticalSection(&_cs); + } + + inline void unlock() + throw() + { + LeaveCriticalSection(&_cs); + } + + inline void lock() const + throw() + { + (const_cast (this))->lock(); + } + + inline void unlock() const + throw() + { + (const_cast (this))->unlock(); + } + + class Lock : NonCopyable + { + public: + Lock(Mutex &m) + throw() : + _m(&m) + { + m.lock(); + } + + Lock(const Mutex &m) + throw() : + _m(const_cast(&m)) + { + _m->lock(); + } + + ~Lock() + { + _m->unlock(); + } + + private: + Mutex *const _m; + }; + +private: + CRITICAL_SECTION _cs; +}; + +} // namespace ZeroTier + +#endif // _WIN32 + +#endif diff --git a/node/Network.cpp b/node/Network.cpp new file mode 100644 index 0000000..b7f25f7 --- /dev/null +++ b/node/Network.cpp @@ -0,0 +1,1631 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include + +#include "Constants.hpp" +#include "../version.h" +#include "Network.hpp" +#include "RuntimeEnvironment.hpp" +#include "MAC.hpp" +#include "Address.hpp" +#include "InetAddress.hpp" +#include "Switch.hpp" +#include "Buffer.hpp" +#include "Packet.hpp" +#include "NetworkController.hpp" +#include "Node.hpp" +#include "Peer.hpp" +#include "Cluster.hpp" + +// Uncomment to make the rules engine dump trace info to stdout +//#define ZT_RULES_ENGINE_DEBUGGING 1 + +namespace ZeroTier { + +namespace { + +#ifdef ZT_RULES_ENGINE_DEBUGGING +#define FILTER_TRACE(f,...) { Utils::snprintf(dpbuf,sizeof(dpbuf),f,##__VA_ARGS__); dlog.push_back(std::string(dpbuf)); } +static const char *_rtn(const ZT_VirtualNetworkRuleType rt) +{ + switch(rt) { + case ZT_NETWORK_RULE_ACTION_DROP: return "ACTION_DROP"; + case ZT_NETWORK_RULE_ACTION_ACCEPT: return "ACTION_ACCEPT"; + case ZT_NETWORK_RULE_ACTION_TEE: return "ACTION_TEE"; + case ZT_NETWORK_RULE_ACTION_WATCH: return "ACTION_WATCH"; + case ZT_NETWORK_RULE_ACTION_REDIRECT: return "ACTION_REDIRECT"; + case ZT_NETWORK_RULE_ACTION_BREAK: return "ACTION_BREAK"; + case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: return "MATCH_SOURCE_ZEROTIER_ADDRESS"; + case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: return "MATCH_DEST_ZEROTIER_ADDRESS"; + case ZT_NETWORK_RULE_MATCH_VLAN_ID: return "MATCH_VLAN_ID"; + case ZT_NETWORK_RULE_MATCH_VLAN_PCP: return "MATCH_VLAN_PCP"; + case ZT_NETWORK_RULE_MATCH_VLAN_DEI: return "MATCH_VLAN_DEI"; + case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: return "MATCH_MAC_SOURCE"; + case ZT_NETWORK_RULE_MATCH_MAC_DEST: return "MATCH_MAC_DEST"; + case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: return "MATCH_IPV4_SOURCE"; + case ZT_NETWORK_RULE_MATCH_IPV4_DEST: return "MATCH_IPV4_DEST"; + case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE: return "MATCH_IPV6_SOURCE"; + case ZT_NETWORK_RULE_MATCH_IPV6_DEST: return "MATCH_IPV6_DEST"; + case ZT_NETWORK_RULE_MATCH_IP_TOS: return "MATCH_IP_TOS"; + case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: return "MATCH_IP_PROTOCOL"; + case ZT_NETWORK_RULE_MATCH_ETHERTYPE: return "MATCH_ETHERTYPE"; + case ZT_NETWORK_RULE_MATCH_ICMP: return "MATCH_ICMP"; + case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: return "MATCH_IP_SOURCE_PORT_RANGE"; + case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: return "MATCH_IP_DEST_PORT_RANGE"; + case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: return "MATCH_CHARACTERISTICS"; + case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE: return "MATCH_FRAME_SIZE_RANGE"; + case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE: return "MATCH_TAGS_DIFFERENCE"; + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND: return "MATCH_TAGS_BITWISE_AND"; + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR: return "MATCH_TAGS_BITWISE_OR"; + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR: return "MATCH_TAGS_BITWISE_XOR"; + default: return "???"; + } +} +static const void _dumpFilterTrace(const char *ruleName,uint8_t thisSetMatches,bool inbound,const Address &ztSource,const Address &ztDest,const MAC &macSource,const MAC &macDest,const std::vector &dlog,unsigned int frameLen,unsigned int etherType,const char *msg) +{ + static volatile unsigned long cnt = 0; + printf("%.6lu %c %s %s frameLen=%u etherType=%u" ZT_EOL_S, + cnt++, + ((thisSetMatches) ? 'Y' : '.'), + ruleName, + ((inbound) ? "INBOUND" : "OUTBOUND"), + frameLen, + etherType + ); + for(std::vector::const_iterator m(dlog.begin());m!=dlog.end();++m) + printf(" | %s" ZT_EOL_S,m->c_str()); + printf(" + %c %s->%s %.2x:%.2x:%.2x:%.2x:%.2x:%.2x->%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" ZT_EOL_S, + ((thisSetMatches) ? 'Y' : '.'), + ztSource.toString().c_str(), + ztDest.toString().c_str(), + (unsigned int)macSource[0], + (unsigned int)macSource[1], + (unsigned int)macSource[2], + (unsigned int)macSource[3], + (unsigned int)macSource[4], + (unsigned int)macSource[5], + (unsigned int)macDest[0], + (unsigned int)macDest[1], + (unsigned int)macDest[2], + (unsigned int)macDest[3], + (unsigned int)macDest[4], + (unsigned int)macDest[5] + ); + if (msg) + printf(" + (%s)" ZT_EOL_S,msg); + fflush(stdout); +} +#else +#define FILTER_TRACE(f,...) {} +#endif // ZT_RULES_ENGINE_DEBUGGING + +// Returns true if packet appears valid; pos and proto will be set +static bool _ipv6GetPayload(const uint8_t *frameData,unsigned int frameLen,unsigned int &pos,unsigned int &proto) +{ + if (frameLen < 40) + return false; + pos = 40; + proto = frameData[6]; + while (pos <= frameLen) { + switch(proto) { + case 0: // hop-by-hop options + case 43: // routing + case 60: // destination options + case 135: // mobility options + if ((pos + 8) > frameLen) + return false; // invalid! + proto = frameData[pos]; + pos += ((unsigned int)frameData[pos + 1] * 8) + 8; + break; + + //case 44: // fragment -- we currently can't parse these and they are deprecated in IPv6 anyway + //case 50: + //case 51: // IPSec ESP and AH -- we have to stop here since this is encrypted stuff + default: + return true; + } + } + return false; // overflow == invalid +} + +enum _doZtFilterResult +{ + DOZTFILTER_NO_MATCH, + DOZTFILTER_DROP, + DOZTFILTER_REDIRECT, + DOZTFILTER_ACCEPT, + DOZTFILTER_SUPER_ACCEPT +}; +static _doZtFilterResult _doZtFilter( + const RuntimeEnvironment *RR, + const NetworkConfig &nconf, + const Membership *membership, // can be NULL + const bool inbound, + const Address &ztSource, + Address &ztDest, // MUTABLE -- is changed on REDIRECT actions + const MAC &macSource, + const MAC &macDest, + const uint8_t *const frameData, + const unsigned int frameLen, + const unsigned int etherType, + const unsigned int vlanId, + const ZT_VirtualNetworkRule *rules, // cannot be NULL + const unsigned int ruleCount, + Address &cc, // MUTABLE -- set to TEE destination if TEE action is taken or left alone otherwise + unsigned int &ccLength, // MUTABLE -- set to length of packet payload to TEE + bool &ccWatch) // MUTABLE -- set to true for WATCH target as opposed to normal TEE +{ +#ifdef ZT_RULES_ENGINE_DEBUGGING + char dpbuf[1024]; // used by FILTER_TRACE macro + std::vector dlog; +#endif // ZT_RULES_ENGINE_DEBUGGING + + // Set to true if we are a TEE/REDIRECT/WATCH target + bool superAccept = false; + + // The default match state for each set of entries starts as 'true' since an + // ACTION with no MATCH entries preceding it is always taken. + uint8_t thisSetMatches = 1; + + for(unsigned int rn=0;rnidentity.address()) { + if (inbound) { +#ifdef ZT_RULES_ENGINE_DEBUGGING + _dumpFilterTrace(_rtn(rt),thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,"interpreted as super-ACCEPT on inbound since we are target"); +#endif // ZT_RULES_ENGINE_DEBUGGING + return DOZTFILTER_SUPER_ACCEPT; + } else { +#ifdef ZT_RULES_ENGINE_DEBUGGING + _dumpFilterTrace(_rtn(rt),thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,"skipped as no-op on outbound since we are target"); + dlog.clear(); +#endif // ZT_RULES_ENGINE_DEBUGGING + } + } else if (fwdAddr == ztDest) { +#ifdef ZT_RULES_ENGINE_DEBUGGING + _dumpFilterTrace(_rtn(rt),thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,"skipped as no-op because destination is already target"); + dlog.clear(); +#endif // ZT_RULES_ENGINE_DEBUGGING + } else { + if (rt == ZT_NETWORK_RULE_ACTION_REDIRECT) { +#ifdef ZT_RULES_ENGINE_DEBUGGING + _dumpFilterTrace("ACTION_REDIRECT",thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,(const char *)0); +#endif // ZT_RULES_ENGINE_DEBUGGING + ztDest = fwdAddr; + return DOZTFILTER_REDIRECT; + } else { +#ifdef ZT_RULES_ENGINE_DEBUGGING + _dumpFilterTrace(_rtn(rt),thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,(const char *)0); + dlog.clear(); +#endif // ZT_RULES_ENGINE_DEBUGGING + cc = fwdAddr; + ccLength = (rules[rn].v.fwd.length != 0) ? ((frameLen < (unsigned int)rules[rn].v.fwd.length) ? frameLen : (unsigned int)rules[rn].v.fwd.length) : frameLen; + ccWatch = (rt == ZT_NETWORK_RULE_ACTION_WATCH); + } + } + } continue; + + case ZT_NETWORK_RULE_ACTION_BREAK: +#ifdef ZT_RULES_ENGINE_DEBUGGING + _dumpFilterTrace("ACTION_BREAK",thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,(const char *)0); + dlog.clear(); +#endif // ZT_RULES_ENGINE_DEBUGGING + return DOZTFILTER_NO_MATCH; + + // Unrecognized ACTIONs are ignored as no-ops + default: +#ifdef ZT_RULES_ENGINE_DEBUGGING + _dumpFilterTrace(_rtn(rt),thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,(const char *)0); + dlog.clear(); +#endif // ZT_RULES_ENGINE_DEBUGGING + continue; + } + } else { + // If this is an incoming packet and we are a TEE or REDIRECT target, we should + // super-accept if we accept at all. This will cause us to accept redirected or + // tee'd packets in spite of MAC and ZT addressing checks. + if (inbound) { + switch(rt) { + case ZT_NETWORK_RULE_ACTION_TEE: + case ZT_NETWORK_RULE_ACTION_WATCH: + case ZT_NETWORK_RULE_ACTION_REDIRECT: + if (RR->identity.address() == rules[rn].v.fwd.address) + superAccept = true; + break; + default: + break; + } + } + +#ifdef ZT_RULES_ENGINE_DEBUGGING + _dumpFilterTrace(_rtn(rt),thisSetMatches,inbound,ztSource,ztDest,macSource,macDest,dlog,frameLen,etherType,(const char *)0); + dlog.clear(); +#endif // ZT_RULES_ENGINE_DEBUGGING + thisSetMatches = 1; // reset to default true for next batch of entries + continue; + } + } + + // Circuit breaker: no need to evaluate an AND if the set's match state + // is currently false since anything AND false is false. + if ((!thisSetMatches)&&(!(rules[rn].t & 0x40))) + continue; + + // If this was not an ACTION evaluate next MATCH and update thisSetMatches with (AND [result]) + uint8_t thisRuleMatches = 0; + uint64_t ownershipVerificationMask = 1; // this magic value means it hasn't been computed yet -- this is done lazily the first time it's needed + switch(rt) { + case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: + thisRuleMatches = (uint8_t)(rules[rn].v.zt == ztSource.toInt()); + FILTER_TRACE("%u %s %c %.10llx==%.10llx -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),rules[rn].v.zt,ztSource.toInt(),(unsigned int)thisRuleMatches); + break; + case ZT_NETWORK_RULE_MATCH_DEST_ZEROTIER_ADDRESS: + thisRuleMatches = (uint8_t)(rules[rn].v.zt == ztDest.toInt()); + FILTER_TRACE("%u %s %c %.10llx==%.10llx -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),rules[rn].v.zt,ztDest.toInt(),(unsigned int)thisRuleMatches); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_ID: + thisRuleMatches = (uint8_t)(rules[rn].v.vlanId == (uint16_t)vlanId); + FILTER_TRACE("%u %s %c %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.vlanId,(unsigned int)vlanId,(unsigned int)thisRuleMatches); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_PCP: + // NOT SUPPORTED YET + thisRuleMatches = (uint8_t)(rules[rn].v.vlanPcp == 0); + FILTER_TRACE("%u %s %c %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.vlanPcp,0,(unsigned int)thisRuleMatches); + break; + case ZT_NETWORK_RULE_MATCH_VLAN_DEI: + // NOT SUPPORTED YET + thisRuleMatches = (uint8_t)(rules[rn].v.vlanDei == 0); + FILTER_TRACE("%u %s %c %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.vlanDei,0,(unsigned int)thisRuleMatches); + break; + case ZT_NETWORK_RULE_MATCH_MAC_SOURCE: + thisRuleMatches = (uint8_t)(MAC(rules[rn].v.mac,6) == macSource); + FILTER_TRACE("%u %s %c %.12llx=%.12llx -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),rules[rn].v.mac,macSource.toInt(),(unsigned int)thisRuleMatches); + break; + case ZT_NETWORK_RULE_MATCH_MAC_DEST: + thisRuleMatches = (uint8_t)(MAC(rules[rn].v.mac,6) == macDest); + FILTER_TRACE("%u %s %c %.12llx=%.12llx -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),rules[rn].v.mac,macDest.toInt(),(unsigned int)thisRuleMatches); + break; + case ZT_NETWORK_RULE_MATCH_IPV4_SOURCE: + if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)) { + thisRuleMatches = (uint8_t)(InetAddress((const void *)&(rules[rn].v.ipv4.ip),4,rules[rn].v.ipv4.mask).containsAddress(InetAddress((const void *)(frameData + 12),4,0))); + FILTER_TRACE("%u %s %c %s contains %s -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),InetAddress((const void *)&(rules[rn].v.ipv4.ip),4,rules[rn].v.ipv4.mask).toString().c_str(),InetAddress((const void *)(frameData + 12),4,0).toIpString().c_str(),(unsigned int)thisRuleMatches); + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [frame not IPv4] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + break; + case ZT_NETWORK_RULE_MATCH_IPV4_DEST: + if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)) { + thisRuleMatches = (uint8_t)(InetAddress((const void *)&(rules[rn].v.ipv4.ip),4,rules[rn].v.ipv4.mask).containsAddress(InetAddress((const void *)(frameData + 16),4,0))); + FILTER_TRACE("%u %s %c %s contains %s -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),InetAddress((const void *)&(rules[rn].v.ipv4.ip),4,rules[rn].v.ipv4.mask).toString().c_str(),InetAddress((const void *)(frameData + 16),4,0).toIpString().c_str(),(unsigned int)thisRuleMatches); + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [frame not IPv4] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + break; + case ZT_NETWORK_RULE_MATCH_IPV6_SOURCE: + if ((etherType == ZT_ETHERTYPE_IPV6)&&(frameLen >= 40)) { + thisRuleMatches = (uint8_t)(InetAddress((const void *)rules[rn].v.ipv6.ip,16,rules[rn].v.ipv6.mask).containsAddress(InetAddress((const void *)(frameData + 8),16,0))); + FILTER_TRACE("%u %s %c %s contains %s -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),InetAddress((const void *)rules[rn].v.ipv6.ip,16,rules[rn].v.ipv6.mask).toString().c_str(),InetAddress((const void *)(frameData + 8),16,0).toIpString().c_str(),(unsigned int)thisRuleMatches); + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [frame not IPv6] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + break; + case ZT_NETWORK_RULE_MATCH_IPV6_DEST: + if ((etherType == ZT_ETHERTYPE_IPV6)&&(frameLen >= 40)) { + thisRuleMatches = (uint8_t)(InetAddress((const void *)rules[rn].v.ipv6.ip,16,rules[rn].v.ipv6.mask).containsAddress(InetAddress((const void *)(frameData + 24),16,0))); + FILTER_TRACE("%u %s %c %s contains %s -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),InetAddress((const void *)rules[rn].v.ipv6.ip,16,rules[rn].v.ipv6.mask).toString().c_str(),InetAddress((const void *)(frameData + 24),16,0).toIpString().c_str(),(unsigned int)thisRuleMatches); + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [frame not IPv6] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + break; + case ZT_NETWORK_RULE_MATCH_IP_TOS: + if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)) { + //thisRuleMatches = (uint8_t)(rules[rn].v.ipTos == ((frameData[1] & 0xfc) >> 2)); + const uint8_t tosMasked = frameData[1] & rules[rn].v.ipTos.mask; + thisRuleMatches = (uint8_t)((tosMasked >= rules[rn].v.ipTos.value[0])&&(tosMasked <= rules[rn].v.ipTos.value[1])); + FILTER_TRACE("%u %s %c (IPv4) %u&%u==%u-%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)tosMasked,(unsigned int)rules[rn].v.ipTos.mask,(unsigned int)rules[rn].v.ipTos.value[0],(unsigned int)rules[rn].v.ipTos.value[1],(unsigned int)thisRuleMatches); + } else if ((etherType == ZT_ETHERTYPE_IPV6)&&(frameLen >= 40)) { + const uint8_t tosMasked = (((frameData[0] << 4) & 0xf0) | ((frameData[1] >> 4) & 0x0f)) & rules[rn].v.ipTos.mask; + thisRuleMatches = (uint8_t)((tosMasked >= rules[rn].v.ipTos.value[0])&&(tosMasked <= rules[rn].v.ipTos.value[1])); + FILTER_TRACE("%u %s %c (IPv4) %u&%u==%u-%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)tosMasked,(unsigned int)rules[rn].v.ipTos.mask,(unsigned int)rules[rn].v.ipTos.value[0],(unsigned int)rules[rn].v.ipTos.value[1],(unsigned int)thisRuleMatches); + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [frame not IP] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + break; + case ZT_NETWORK_RULE_MATCH_IP_PROTOCOL: + if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)) { + thisRuleMatches = (uint8_t)(rules[rn].v.ipProtocol == frameData[9]); + FILTER_TRACE("%u %s %c (IPv4) %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.ipProtocol,(unsigned int)frameData[9],(unsigned int)thisRuleMatches); + } else if (etherType == ZT_ETHERTYPE_IPV6) { + unsigned int pos = 0,proto = 0; + if (_ipv6GetPayload(frameData,frameLen,pos,proto)) { + thisRuleMatches = (uint8_t)(rules[rn].v.ipProtocol == (uint8_t)proto); + FILTER_TRACE("%u %s %c (IPv6) %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.ipProtocol,proto,(unsigned int)thisRuleMatches); + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [invalid IPv6] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [frame not IP] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + break; + case ZT_NETWORK_RULE_MATCH_ETHERTYPE: + thisRuleMatches = (uint8_t)(rules[rn].v.etherType == (uint16_t)etherType); + FILTER_TRACE("%u %s %c %u==%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.etherType,etherType,(unsigned int)thisRuleMatches); + break; + case ZT_NETWORK_RULE_MATCH_ICMP: + if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)) { + if (frameData[9] == 0x01) { // IP protocol == ICMP + const unsigned int ihl = (frameData[0] & 0xf) * 4; + if (frameLen >= (ihl + 2)) { + if (rules[rn].v.icmp.type == frameData[ihl]) { + if ((rules[rn].v.icmp.flags & 0x01) != 0) { + thisRuleMatches = (uint8_t)(frameData[ihl+1] == rules[rn].v.icmp.code); + } else { + thisRuleMatches = 1; + } + } else { + thisRuleMatches = 0; + } + FILTER_TRACE("%u %s %c (IPv4) icmp-type:%d==%d icmp-code:%d==%d -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(int)frameData[ihl],(int)rules[rn].v.icmp.type,(int)frameData[ihl+1],(((rules[rn].v.icmp.flags & 0x01) != 0) ? (int)rules[rn].v.icmp.code : -1),(unsigned int)thisRuleMatches); + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [IPv4 frame invalid] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [frame not ICMP] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + } else if (etherType == ZT_ETHERTYPE_IPV6) { + unsigned int pos = 0,proto = 0; + if (_ipv6GetPayload(frameData,frameLen,pos,proto)) { + if ((proto == 0x3a)&&(frameLen >= (pos+2))) { + if (rules[rn].v.icmp.type == frameData[pos]) { + if ((rules[rn].v.icmp.flags & 0x01) != 0) { + thisRuleMatches = (uint8_t)(frameData[pos+1] == rules[rn].v.icmp.code); + } else { + thisRuleMatches = 1; + } + } else { + thisRuleMatches = 0; + } + FILTER_TRACE("%u %s %c (IPv6) icmp-type:%d==%d icmp-code:%d==%d -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(int)frameData[pos],(int)rules[rn].v.icmp.type,(int)frameData[pos+1],(((rules[rn].v.icmp.flags & 0x01) != 0) ? (int)rules[rn].v.icmp.code : -1),(unsigned int)thisRuleMatches); + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [frame not ICMPv6] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [invalid IPv6] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [frame not IP] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + break; + break; + case ZT_NETWORK_RULE_MATCH_IP_SOURCE_PORT_RANGE: + case ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE: + if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)) { + const unsigned int headerLen = 4 * (frameData[0] & 0xf); + int p = -1; + switch(frameData[9]) { // IP protocol number + // All these start with 16-bit source and destination port in that order + case 0x06: // TCP + case 0x11: // UDP + case 0x84: // SCTP + case 0x88: // UDPLite + if (frameLen > (headerLen + 4)) { + unsigned int pos = headerLen + ((rt == ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE) ? 2 : 0); + p = (int)frameData[pos++] << 8; + p |= (int)frameData[pos]; + } + break; + } + + thisRuleMatches = (p >= 0) ? (uint8_t)((p >= (int)rules[rn].v.port[0])&&(p <= (int)rules[rn].v.port[1])) : (uint8_t)0; + FILTER_TRACE("%u %s %c (IPv4) %d in %d-%d -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),p,(int)rules[rn].v.port[0],(int)rules[rn].v.port[1],(unsigned int)thisRuleMatches); + } else if (etherType == ZT_ETHERTYPE_IPV6) { + unsigned int pos = 0,proto = 0; + if (_ipv6GetPayload(frameData,frameLen,pos,proto)) { + int p = -1; + switch(proto) { // IP protocol number + // All these start with 16-bit source and destination port in that order + case 0x06: // TCP + case 0x11: // UDP + case 0x84: // SCTP + case 0x88: // UDPLite + if (frameLen > (pos + 4)) { + if (rt == ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE) pos += 2; + p = (int)frameData[pos++] << 8; + p |= (int)frameData[pos]; + } + break; + } + thisRuleMatches = (p > 0) ? (uint8_t)((p >= (int)rules[rn].v.port[0])&&(p <= (int)rules[rn].v.port[1])) : (uint8_t)0; + FILTER_TRACE("%u %s %c (IPv6) %d in %d-%d -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),p,(int)rules[rn].v.port[0],(int)rules[rn].v.port[1],(unsigned int)thisRuleMatches); + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [invalid IPv6] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c [frame not IP] -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } + break; + case ZT_NETWORK_RULE_MATCH_CHARACTERISTICS: { + uint64_t cf = (inbound) ? ZT_RULE_PACKET_CHARACTERISTICS_INBOUND : 0ULL; + if (macDest.isMulticast()) cf |= ZT_RULE_PACKET_CHARACTERISTICS_MULTICAST; + if (macDest.isBroadcast()) cf |= ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST; + if (ownershipVerificationMask == 1) { + ownershipVerificationMask = 0; + InetAddress src; + if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)) { + src.set((const void *)(frameData + 12),4,0); + } else if ((etherType == ZT_ETHERTYPE_IPV6)&&(frameLen >= 40)) { + // IPv6 NDP requires special handling, since the src and dest IPs in the packet are empty or link-local. + if ( (frameLen >= (40 + 8 + 16)) && (frameData[6] == 0x3a) && ((frameData[40] == 0x87)||(frameData[40] == 0x88)) ) { + if (frameData[40] == 0x87) { + // Neighbor solicitations contain no reliable source address, so we implement a small + // hack by considering them authenticated. Otherwise you would pretty much have to do + // this manually in the rule set for IPv6 to work at all. + ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED; + } else { + // Neighbor advertisements on the other hand can absolutely be authenticated. + src.set((const void *)(frameData + 40 + 8),16,0); + } + } else { + // Other IPv6 packets can be handled normally + src.set((const void *)(frameData + 8),16,0); + } + } else if ((etherType == ZT_ETHERTYPE_ARP)&&(frameLen >= 28)) { + src.set((const void *)(frameData + 14),4,0); + } + if (inbound) { + if (membership) { + if ((src)&&(membership->hasCertificateOfOwnershipFor(nconf,src))) + ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED; + if (membership->hasCertificateOfOwnershipFor(nconf,macSource)) + ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_MAC_AUTHENTICATED; + } + } else { + for(unsigned int i=0;i= 20)&&(frameData[9] == 0x06)) { + const unsigned int headerLen = 4 * (frameData[0] & 0xf); + cf |= (uint64_t)frameData[headerLen + 13]; + cf |= (((uint64_t)(frameData[headerLen + 12] & 0x0f)) << 8); + } else if (etherType == ZT_ETHERTYPE_IPV6) { + unsigned int pos = 0,proto = 0; + if (_ipv6GetPayload(frameData,frameLen,pos,proto)) { + if ((proto == 0x06)&&(frameLen > (pos + 14))) { + cf |= (uint64_t)frameData[pos + 13]; + cf |= (((uint64_t)(frameData[pos + 12] & 0x0f)) << 8); + } + } + } + thisRuleMatches = (uint8_t)((cf & rules[rn].v.characteristics) != 0); + FILTER_TRACE("%u %s %c (%.16llx | %.16llx)!=0 -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),cf,rules[rn].v.characteristics,(unsigned int)thisRuleMatches); + } break; + case ZT_NETWORK_RULE_MATCH_FRAME_SIZE_RANGE: + thisRuleMatches = (uint8_t)((frameLen >= (unsigned int)rules[rn].v.frameSize[0])&&(frameLen <= (unsigned int)rules[rn].v.frameSize[1])); + FILTER_TRACE("%u %s %c %u in %u-%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),frameLen,(unsigned int)rules[rn].v.frameSize[0],(unsigned int)rules[rn].v.frameSize[1],(unsigned int)thisRuleMatches); + break; + case ZT_NETWORK_RULE_MATCH_RANDOM: + thisRuleMatches = (uint8_t)((uint32_t)(RR->node->prng() & 0xffffffffULL) <= rules[rn].v.randomProbability); + FILTER_TRACE("%u %s %c -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)thisRuleMatches); + break; + case ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE: + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND: + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR: + case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR: + case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL: { + const Tag *const localTag = std::lower_bound(&(nconf.tags[0]),&(nconf.tags[nconf.tagCount]),rules[rn].v.tag.id,Tag::IdComparePredicate()); + if ((localTag != &(nconf.tags[nconf.tagCount]))&&(localTag->id() == rules[rn].v.tag.id)) { + const Tag *const remoteTag = ((membership) ? membership->getTag(nconf,rules[rn].v.tag.id) : (const Tag *)0); + if (remoteTag) { + const uint32_t ltv = localTag->value(); + const uint32_t rtv = remoteTag->value(); + if (rt == ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE) { + const uint32_t diff = (ltv > rtv) ? (ltv - rtv) : (rtv - ltv); + thisRuleMatches = (uint8_t)(diff <= rules[rn].v.tag.value); + FILTER_TRACE("%u %s %c TAG %u local:%u remote:%u difference:%u<=%u -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,diff,(unsigned int)rules[rn].v.tag.value,thisRuleMatches); + } else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND) { + thisRuleMatches = (uint8_t)((ltv & rtv) == rules[rn].v.tag.value); + FILTER_TRACE("%u %s %c TAG %u local:%.8x & remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches); + } else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR) { + thisRuleMatches = (uint8_t)((ltv | rtv) == rules[rn].v.tag.value); + FILTER_TRACE("%u %s %c TAG %u local:%.8x | remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches); + } else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR) { + thisRuleMatches = (uint8_t)((ltv ^ rtv) == rules[rn].v.tag.value); + FILTER_TRACE("%u %s %c TAG %u local:%.8x ^ remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches); + } else if (rt == ZT_NETWORK_RULE_MATCH_TAGS_EQUAL) { + thisRuleMatches = (uint8_t)((ltv == rules[rn].v.tag.value)&&(rtv == rules[rn].v.tag.value)); + FILTER_TRACE("%u %s %c TAG %u local:%.8x and remote:%.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,ltv,rtv,(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches); + } else { // sanity check, can't really happen + thisRuleMatches = 0; + } + } else { + if ((inbound)&&(!superAccept)) { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c remote tag %u not found -> 0 (inbound side is strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); + } else { + // Outbound side is not strict since if we have to match both tags and + // we are sending a first packet to a recipient, we probably do not know + // about their tags yet. They will filter on inbound and we will filter + // once we get their tag. If we are a tee/redirect target we are also + // not strict since we likely do not have these tags. + thisRuleMatches = 1; + FILTER_TRACE("%u %s %c remote tag %u not found -> 1 (outbound side and TEE/REDIRECT targets are not strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); + } + } + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c local tag %u not found -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); + } + } break; + case ZT_NETWORK_RULE_MATCH_TAG_SENDER: + case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER: { + if (superAccept) { + thisRuleMatches = 1; + FILTER_TRACE("%u %s %c we are a TEE/REDIRECT target -> 1",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } else if ( ((rt == ZT_NETWORK_RULE_MATCH_TAG_SENDER)&&(inbound)) || ((rt == ZT_NETWORK_RULE_MATCH_TAG_RECEIVER)&&(!inbound)) ) { + const Tag *const remoteTag = ((membership) ? membership->getTag(nconf,rules[rn].v.tag.id) : (const Tag *)0); + if (remoteTag) { + thisRuleMatches = (uint8_t)(remoteTag->value() == rules[rn].v.tag.value); + FILTER_TRACE("%u %s %c TAG %u %.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,remoteTag->value(),(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches); + } else { + if (rt == ZT_NETWORK_RULE_MATCH_TAG_RECEIVER) { + // If we are checking the receiver and this is an outbound packet, we + // can't be strict since we may not yet know the receiver's tag. + thisRuleMatches = 1; + FILTER_TRACE("%u %s %c (inbound) remote tag %u not found -> 1 (outbound receiver match is not strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c (inbound) remote tag %u not found -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); + } + } + } else { // sender and outbound or receiver and inbound + const Tag *const localTag = std::lower_bound(&(nconf.tags[0]),&(nconf.tags[nconf.tagCount]),rules[rn].v.tag.id,Tag::IdComparePredicate()); + if ((localTag != &(nconf.tags[nconf.tagCount]))&&(localTag->id() == rules[rn].v.tag.id)) { + thisRuleMatches = (uint8_t)(localTag->value() == rules[rn].v.tag.value); + FILTER_TRACE("%u %s %c TAG %u %.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,localTag->value(),(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches); + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c local tag %u not found -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); + } + } + } break; + + // The result of an unsupported MATCH is configurable at the network + // level via a flag. + default: + thisRuleMatches = (uint8_t)((nconf.flags & ZT_NETWORKCONFIG_FLAG_RULES_RESULT_OF_UNSUPPORTED_MATCH) != 0); + break; + } + + if ((rules[rn].t & 0x40)) + thisSetMatches |= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1)); + else thisSetMatches &= (thisRuleMatches ^ ((rules[rn].t >> 7) & 1)); + } + + return DOZTFILTER_NO_MATCH; +} + +} // anonymous namespace + +const ZeroTier::MulticastGroup Network::BROADCAST(ZeroTier::MAC(0xffffffffffffULL),0); + +Network::Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *uptr) : + RR(renv), + _uPtr(uptr), + _id(nwid), + _lastAnnouncedMulticastGroupsUpstream(0), + _mac(renv->identity.address(),nwid), + _portInitialized(false), + _lastConfigUpdate(0), + _destroyed(false), + _netconfFailure(NETCONF_FAILURE_NONE), + _portError(0) +{ + for(int i=0;i *dconf = new Dictionary(); + NetworkConfig *nconf = new NetworkConfig(); + try { + std::string conf(RR->node->dataStoreGet(tPtr,confn)); + if (conf.length()) { + dconf->load(conf.c_str()); + if (nconf->fromDictionary(*dconf)) { + this->setConfiguration(tPtr,*nconf,false); + _lastConfigUpdate = 0; // we still want to re-request a new config from the network + gotConf = true; + } + } + } catch ( ... ) {} // ignore invalids, we'll re-request + delete nconf; + delete dconf; + + if (!gotConf) { + // Save a one-byte CR to persist membership while we request a real netconf + RR->node->dataStorePut(tPtr,confn,"\n",1,false); + } + + if (!_portInitialized) { + ZT_VirtualNetworkConfig ctmp; + _externalConfig(&ctmp); + _portError = RR->node->configureVirtualNetworkPort(tPtr,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp); + _portInitialized = true; + } +} + +Network::~Network() +{ + ZT_VirtualNetworkConfig ctmp; + _externalConfig(&ctmp); + + char n[128]; + if (_destroyed) { + // This is done in Node::leave() so we can pass tPtr + //RR->node->configureVirtualNetworkPort((void *)0,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp); + Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id); + RR->node->dataStoreDelete((void *)0,n); + } else { + RR->node->configureVirtualNetworkPort((void *)0,_id,&_uPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN,&ctmp); + } +} + +bool Network::filterOutgoingPacket( + void *tPtr, + const bool noTee, + const Address &ztSource, + const Address &ztDest, + const MAC &macSource, + const MAC &macDest, + const uint8_t *frameData, + const unsigned int frameLen, + const unsigned int etherType, + const unsigned int vlanId) +{ + const uint64_t now = RR->node->now(); + Address ztFinalDest(ztDest); + int localCapabilityIndex = -1; + bool accept = false; + + Mutex::Lock _l(_lock); + + Membership *const membership = (ztDest) ? _memberships.get(ztDest) : (Membership *)0; + + Address cc; + unsigned int ccLength = 0; + bool ccWatch = false; + switch(_doZtFilter(RR,_config,membership,false,ztSource,ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,cc,ccLength,ccWatch)) { + + case DOZTFILTER_NO_MATCH: + for(unsigned int c=0;c<_config.capabilityCount;++c) { + ztFinalDest = ztDest; // sanity check, shouldn't be possible if there was no match + Address cc2; + unsigned int ccLength2 = 0; + bool ccWatch2 = false; + switch (_doZtFilter(RR,_config,membership,false,ztSource,ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.capabilities[c].rules(),_config.capabilities[c].ruleCount(),cc2,ccLength2,ccWatch2)) { + case DOZTFILTER_NO_MATCH: + case DOZTFILTER_DROP: // explicit DROP in a capability just terminates its evaluation and is an anti-pattern + break; + + case DOZTFILTER_REDIRECT: // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter() + case DOZTFILTER_ACCEPT: + case DOZTFILTER_SUPER_ACCEPT: // no difference in behavior on outbound side + localCapabilityIndex = (int)c; + accept = true; + + if ((!noTee)&&(cc2)) { + Membership &m2 = _membership(cc2); + m2.pushCredentials(RR,tPtr,now,cc2,_config,localCapabilityIndex,false); + + Packet outp(cc2,RR->identity.address(),Packet::VERB_EXT_FRAME); + outp.append(_id); + outp.append((uint8_t)(ccWatch2 ? 0x16 : 0x02)); + macDest.appendTo(outp); + macSource.appendTo(outp); + outp.append((uint16_t)etherType); + outp.append(frameData,ccLength2); + outp.compress(); + RR->sw->send(tPtr,outp,true); + } + + break; + } + if (accept) + break; + } + break; + + case DOZTFILTER_DROP: + return false; + + case DOZTFILTER_REDIRECT: // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter() + case DOZTFILTER_ACCEPT: + case DOZTFILTER_SUPER_ACCEPT: // no difference in behavior on outbound side + accept = true; + break; + } + + if (accept) { + if (membership) + membership->pushCredentials(RR,tPtr,now,ztDest,_config,localCapabilityIndex,false); + + if ((!noTee)&&(cc)) { + Membership &m2 = _membership(cc); + m2.pushCredentials(RR,tPtr,now,cc,_config,localCapabilityIndex,false); + + Packet outp(cc,RR->identity.address(),Packet::VERB_EXT_FRAME); + outp.append(_id); + outp.append((uint8_t)(ccWatch ? 0x16 : 0x02)); + macDest.appendTo(outp); + macSource.appendTo(outp); + outp.append((uint16_t)etherType); + outp.append(frameData,ccLength); + outp.compress(); + RR->sw->send(tPtr,outp,true); + } + + if ((ztDest != ztFinalDest)&&(ztFinalDest)) { + Membership &m2 = _membership(ztFinalDest); + m2.pushCredentials(RR,tPtr,now,ztFinalDest,_config,localCapabilityIndex,false); + + Packet outp(ztFinalDest,RR->identity.address(),Packet::VERB_EXT_FRAME); + outp.append(_id); + outp.append((uint8_t)0x04); + macDest.appendTo(outp); + macSource.appendTo(outp); + outp.append((uint16_t)etherType); + outp.append(frameData,frameLen); + outp.compress(); + RR->sw->send(tPtr,outp,true); + + return false; // DROP locally, since we redirected + } else { + return true; + } + } else { + return false; + } +} + +int Network::filterIncomingPacket( + void *tPtr, + const SharedPtr &sourcePeer, + const Address &ztDest, + const MAC &macSource, + const MAC &macDest, + const uint8_t *frameData, + const unsigned int frameLen, + const unsigned int etherType, + const unsigned int vlanId) +{ + Address ztFinalDest(ztDest); + int accept = 0; + + Mutex::Lock _l(_lock); + + Membership &membership = _membership(sourcePeer->address()); + + Address cc; + unsigned int ccLength = 0; + bool ccWatch = false; + switch (_doZtFilter(RR,_config,&membership,true,sourcePeer->address(),ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,_config.rules,_config.ruleCount,cc,ccLength,ccWatch)) { + + case DOZTFILTER_NO_MATCH: { + Membership::CapabilityIterator mci(membership,_config); + const Capability *c; + while ((c = mci.next())) { + ztFinalDest = ztDest; // sanity check, should be unmodified if there was no match + Address cc2; + unsigned int ccLength2 = 0; + bool ccWatch2 = false; + switch(_doZtFilter(RR,_config,&membership,true,sourcePeer->address(),ztFinalDest,macSource,macDest,frameData,frameLen,etherType,vlanId,c->rules(),c->ruleCount(),cc2,ccLength2,ccWatch2)) { + case DOZTFILTER_NO_MATCH: + case DOZTFILTER_DROP: // explicit DROP in a capability just terminates its evaluation and is an anti-pattern + break; + case DOZTFILTER_REDIRECT: // interpreted as ACCEPT but ztDest will have been changed in _doZtFilter() + case DOZTFILTER_ACCEPT: + accept = 1; // ACCEPT + break; + case DOZTFILTER_SUPER_ACCEPT: + accept = 2; // super-ACCEPT + break; + } + + if (accept) { + if (cc2) { + _membership(cc2).pushCredentials(RR,tPtr,RR->node->now(),cc2,_config,-1,false); + + Packet outp(cc2,RR->identity.address(),Packet::VERB_EXT_FRAME); + outp.append(_id); + outp.append((uint8_t)(ccWatch2 ? 0x1c : 0x08)); + macDest.appendTo(outp); + macSource.appendTo(outp); + outp.append((uint16_t)etherType); + outp.append(frameData,ccLength2); + outp.compress(); + RR->sw->send(tPtr,outp,true); + } + break; + } + } + } break; + + case DOZTFILTER_DROP: + return 0; // DROP + + case DOZTFILTER_REDIRECT: // interpreted as ACCEPT but ztFinalDest will have been changed in _doZtFilter() + case DOZTFILTER_ACCEPT: + accept = 1; // ACCEPT + break; + case DOZTFILTER_SUPER_ACCEPT: + accept = 2; // super-ACCEPT + break; + } + + if (accept) { + if (cc) { + _membership(cc).pushCredentials(RR,tPtr,RR->node->now(),cc,_config,-1,false); + + Packet outp(cc,RR->identity.address(),Packet::VERB_EXT_FRAME); + outp.append(_id); + outp.append((uint8_t)(ccWatch ? 0x1c : 0x08)); + macDest.appendTo(outp); + macSource.appendTo(outp); + outp.append((uint16_t)etherType); + outp.append(frameData,ccLength); + outp.compress(); + RR->sw->send(tPtr,outp,true); + } + + if ((ztDest != ztFinalDest)&&(ztFinalDest)) { + _membership(ztFinalDest).pushCredentials(RR,tPtr,RR->node->now(),ztFinalDest,_config,-1,false); + + Packet outp(ztFinalDest,RR->identity.address(),Packet::VERB_EXT_FRAME); + outp.append(_id); + outp.append((uint8_t)0x0a); + macDest.appendTo(outp); + macSource.appendTo(outp); + outp.append((uint16_t)etherType); + outp.append(frameData,frameLen); + outp.compress(); + RR->sw->send(tPtr,outp,true); + + return 0; // DROP locally, since we redirected + } + } + + return accept; +} + +bool Network::subscribedToMulticastGroup(const MulticastGroup &mg,bool includeBridgedGroups) const +{ + Mutex::Lock _l(_lock); + if (std::binary_search(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)) + return true; + else if (includeBridgedGroups) + return _multicastGroupsBehindMe.contains(mg); + return false; +} + +void Network::multicastSubscribe(void *tPtr,const MulticastGroup &mg) +{ + Mutex::Lock _l(_lock); + if (!std::binary_search(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)) { + _myMulticastGroups.insert(std::upper_bound(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg),mg); + _sendUpdatesToMembers(tPtr,&mg); + } +} + +void Network::multicastUnsubscribe(const MulticastGroup &mg) +{ + Mutex::Lock _l(_lock); + std::vector::iterator i(std::lower_bound(_myMulticastGroups.begin(),_myMulticastGroups.end(),mg)); + if ( (i != _myMulticastGroups.end()) && (*i == mg) ) + _myMulticastGroups.erase(i); +} + +uint64_t Network::handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr) +{ + if (_destroyed) + return 0; + + const unsigned int start = ptr; + + ptr += 8; // skip network ID, which is already obviously known + const unsigned int chunkLen = chunk.at(ptr); ptr += 2; + const void *chunkData = chunk.field(ptr,chunkLen); ptr += chunkLen; + + NetworkConfig *nc = (NetworkConfig *)0; + uint64_t configUpdateId; + { + Mutex::Lock _l(_lock); + + _IncomingConfigChunk *c = (_IncomingConfigChunk *)0; + uint64_t chunkId = 0; + unsigned long totalLength,chunkIndex; + if (ptr < chunk.size()) { + const bool fastPropagate = ((chunk[ptr++] & 0x01) != 0); + configUpdateId = chunk.at(ptr); ptr += 8; + totalLength = chunk.at(ptr); ptr += 4; + chunkIndex = chunk.at(ptr); ptr += 4; + + if (((chunkIndex + chunkLen) > totalLength)||(totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY)) { // >= since we need room for a null at the end + TRACE("discarded chunk from %s: invalid length or length overflow",source.toString().c_str()); + return 0; + } + + if ((chunk[ptr] != 1)||(chunk.at(ptr + 1) != ZT_C25519_SIGNATURE_LEN)) { + TRACE("discarded chunk from %s: unrecognized signature type",source.toString().c_str()); + return 0; + } + const uint8_t *sig = reinterpret_cast(chunk.field(ptr + 3,ZT_C25519_SIGNATURE_LEN)); + + // We can use the signature, which is unique per chunk, to get a per-chunk ID for local deduplication use + for(unsigned int i=0;i<16;++i) + reinterpret_cast(&chunkId)[i & 7] ^= sig[i]; + + // Find existing or new slot for this update and check if this is a duplicate chunk + for(int i=0;ihaveChunks;++j) { + if (c->haveChunkIds[j] == chunkId) + return 0; + } + + break; + } else if ((!c)||(_incomingConfigChunks[i].ts < c->ts)) { + c = &(_incomingConfigChunks[i]); + } + } + + // If it's not a duplicate, check chunk signature + const Identity controllerId(RR->topology->getIdentity(tPtr,controller())); + if (!controllerId) { // we should always have the controller identity by now, otherwise how would we have queried it the first time? + TRACE("unable to verify chunk from %s: don't have controller identity",source.toString().c_str()); + return 0; + } + if (!controllerId.verify(chunk.field(start,ptr - start),ptr - start,sig,ZT_C25519_SIGNATURE_LEN)) { + TRACE("discarded chunk from %s: signature check failed",source.toString().c_str()); + return 0; + } + +#ifdef ZT_ENABLE_CLUSTER + if ((source)&&(RR->cluster)) + RR->cluster->broadcastNetworkConfigChunk(chunk.field(start,chunk.size() - start),chunk.size() - start); +#endif + + // New properly verified chunks can be flooded "virally" through the network + if (fastPropagate) { + Address *a = (Address *)0; + Membership *m = (Membership *)0; + Hashtable::Iterator i(_memberships); + while (i.next(a,m)) { + if ((*a != source)&&(*a != controller())) { + Packet outp(*a,RR->identity.address(),Packet::VERB_NETWORK_CONFIG); + outp.append(reinterpret_cast(chunk.data()) + start,chunk.size() - start); + RR->sw->send(tPtr,outp,true); + } + } + } + } else if ((source == controller())||(!source)) { // since old chunks aren't signed, only accept from controller itself (or via cluster backplane) + // Legacy support for OK(NETWORK_CONFIG_REQUEST) from older controllers + chunkId = packetId; + configUpdateId = chunkId; + totalLength = chunkLen; + chunkIndex = 0; + + if (totalLength >= ZT_NETWORKCONFIG_DICT_CAPACITY) + return 0; + + for(int i=0;its)) + c = &(_incomingConfigChunks[i]); + } + +#ifdef ZT_ENABLE_CLUSTER + if ((source)&&(RR->cluster)) + RR->cluster->broadcastNetworkConfigChunk(chunk.field(start,chunk.size() - start),chunk.size() - start); +#endif + } else { + TRACE("discarded single-chunk unsigned legacy config: this is only allowed if the sender is the controller itself"); + return 0; + } + + ++c->ts; // newer is higher, that's all we need + + if (c->updateId != configUpdateId) { + c->updateId = configUpdateId; + c->haveChunks = 0; + c->haveBytes = 0; + } + if (c->haveChunks >= ZT_NETWORK_MAX_UPDATE_CHUNKS) + return false; + c->haveChunkIds[c->haveChunks++] = chunkId; + + memcpy(c->data.unsafeData() + chunkIndex,chunkData,chunkLen); + c->haveBytes += chunkLen; + + if (c->haveBytes == totalLength) { + c->data.unsafeData()[c->haveBytes] = (char)0; // ensure null terminated + + nc = new NetworkConfig(); + try { + if (!nc->fromDictionary(c->data)) { + delete nc; + nc = (NetworkConfig *)0; + } + } catch ( ... ) { + delete nc; + nc = (NetworkConfig *)0; + } + } + } + + if (nc) { + this->setConfiguration(tPtr,*nc,true); + delete nc; + return configUpdateId; + } else { + return 0; + } + + return 0; +} + +int Network::setConfiguration(void *tPtr,const NetworkConfig &nconf,bool saveToDisk) +{ + if (_destroyed) + return 0; + + // _lock is NOT locked when this is called + try { + if ((nconf.issuedTo != RR->identity.address())||(nconf.networkId != _id)) + return 0; // invalid config that is not for us or not for this network + if (_config == nconf) + return 1; // OK config, but duplicate of what we already have + + ZT_VirtualNetworkConfig ctmp; + bool oldPortInitialized; + { // do things that require lock here, but unlock before calling callbacks + Mutex::Lock _l(_lock); + + _config = nconf; + _lastConfigUpdate = RR->node->now(); + _netconfFailure = NETCONF_FAILURE_NONE; + + oldPortInitialized = _portInitialized; + _portInitialized = true; + + _externalConfig(&ctmp); + + Address *a = (Address *)0; + Membership *m = (Membership *)0; + Hashtable::Iterator i(_memberships); + while (i.next(a,m)) + m->resetPushState(); + } + + _portError = RR->node->configureVirtualNetworkPort(tPtr,_id,&_uPtr,(oldPortInitialized) ? ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE : ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP,&ctmp); + + if (saveToDisk) { + Dictionary *d = new Dictionary(); + try { + char n[64]; + Utils::snprintf(n,sizeof(n),"networks.d/%.16llx.conf",_id); + if (nconf.toDictionary(*d,false)) + RR->node->dataStorePut(tPtr,n,(const void *)d->data(),d->sizeBytes(),true); + } catch ( ... ) {} + delete d; + } + + return 2; // OK and configuration has changed + } catch ( ... ) { + TRACE("ignored invalid configuration for network %.16llx",(unsigned long long)_id); + } + return 0; +} + +void Network::requestConfiguration(void *tPtr) +{ + if (_destroyed) + return; + + /* ZeroTier addresses can't begin with 0xff, so this is used to mark controllerless + * network IDs. Controllerless network IDs only support unicast IPv6 using the 6plane + * addressing scheme and have the following format: 0xffSSSSEEEE000000 where SSSS + * is the 16-bit starting IP port range allowed and EEEE is the 16-bit ending IP port + * range allowed. Remaining digits are reserved for future use and must be zero. */ + if ((_id >> 56) == 0xff) { + const uint16_t startPortRange = (uint16_t)((_id >> 40) & 0xffff); + const uint16_t endPortRange = (uint16_t)((_id >> 24) & 0xffff); + if (((_id & 0xffffff) == 0)&&(endPortRange >= startPortRange)) { + NetworkConfig *const nconf = new NetworkConfig(); + + nconf->networkId = _id; + nconf->timestamp = RR->node->now(); + nconf->credentialTimeMaxDelta = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA; + nconf->revision = 1; + nconf->issuedTo = RR->identity.address(); + nconf->flags = ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; + nconf->staticIpCount = 1; + nconf->ruleCount = 14; + nconf->staticIps[0] = InetAddress::makeIpv66plane(_id,RR->identity.address().toInt()); + + // Drop everything but IPv6 + nconf->rules[0].t = (uint8_t)ZT_NETWORK_RULE_MATCH_ETHERTYPE | 0x80; // NOT + nconf->rules[0].v.etherType = 0x86dd; // IPv6 + nconf->rules[1].t = (uint8_t)ZT_NETWORK_RULE_ACTION_DROP; + + // Allow ICMPv6 + nconf->rules[2].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL; + nconf->rules[2].v.ipProtocol = 0x3a; // ICMPv6 + nconf->rules[3].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT; + + // Allow destination ports within range + nconf->rules[4].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL; + nconf->rules[4].v.ipProtocol = 0x11; // UDP + nconf->rules[5].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL | 0x40; // OR + nconf->rules[5].v.ipProtocol = 0x06; // TCP + nconf->rules[6].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE; + nconf->rules[6].v.port[0] = startPortRange; + nconf->rules[6].v.port[1] = endPortRange; + nconf->rules[7].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT; + + // Allow non-SYN TCP packets to permit non-connection-initiating traffic + nconf->rules[8].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS | 0x80; // NOT + nconf->rules[8].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_SYN; + nconf->rules[9].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT; + + // Also allow SYN+ACK which are replies to SYN + nconf->rules[10].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS; + nconf->rules[10].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_SYN; + nconf->rules[11].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS; + nconf->rules[11].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_ACK; + nconf->rules[12].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT; + + nconf->rules[13].t = (uint8_t)ZT_NETWORK_RULE_ACTION_DROP; + + nconf->type = ZT_NETWORK_TYPE_PUBLIC; + Utils::snprintf(nconf->name,sizeof(nconf->name),"adhoc-%.04x-%.04x",(int)startPortRange,(int)endPortRange); + + this->setConfiguration(tPtr,*nconf,false); + delete nconf; + } else { + this->setNotFound(); + } + return; + } + + const Address ctrl(controller()); + + Dictionary rmd; + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION,(uint64_t)ZT_NETWORKCONFIG_VERSION); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_VENDOR,(uint64_t)ZT_VENDOR_ZEROTIER); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION,(uint64_t)ZT_PROTO_VERSION); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION,(uint64_t)ZEROTIER_ONE_VERSION_MAJOR); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION,(uint64_t)ZEROTIER_ONE_VERSION_MINOR); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION,(uint64_t)ZEROTIER_ONE_VERSION_REVISION); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_RULES,(uint64_t)ZT_MAX_NETWORK_RULES); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_CAPABILITIES,(uint64_t)ZT_MAX_NETWORK_CAPABILITIES); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_CAPABILITY_RULES,(uint64_t)ZT_MAX_CAPABILITY_RULES); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_TAGS,(uint64_t)ZT_MAX_NETWORK_TAGS); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_FLAGS,(uint64_t)0); + rmd.add(ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV,(uint64_t)ZT_RULES_ENGINE_REVISION); + + if (ctrl == RR->identity.address()) { + if (RR->localNetworkController) { + RR->localNetworkController->request(_id,InetAddress(),0xffffffffffffffffULL,RR->identity,rmd); + } else { + this->setNotFound(); + } + return; + } + + TRACE("requesting netconf for network %.16llx from controller %s",(unsigned long long)_id,ctrl.toString().c_str()); + + Packet outp(ctrl,RR->identity.address(),Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append((uint64_t)_id); + const unsigned int rmdSize = rmd.sizeBytes(); + outp.append((uint16_t)rmdSize); + outp.append((const void *)rmd.data(),rmdSize); + if (_config) { + outp.append((uint64_t)_config.revision); + outp.append((uint64_t)_config.timestamp); + } else { + outp.append((unsigned char)0,16); + } + outp.compress(); + RR->node->expectReplyTo(outp.packetId()); + RR->sw->send(tPtr,outp,true); +} + +bool Network::gate(void *tPtr,const SharedPtr &peer) +{ + const uint64_t now = RR->node->now(); + Mutex::Lock _l(_lock); + try { + if (_config) { + Membership *m = _memberships.get(peer->address()); + if ( (_config.isPublic()) || ((m)&&(m->isAllowedOnNetwork(_config))) ) { + if (!m) + m = &(_membership(peer->address())); + if (m->multicastLikeGate(now)) { + m->pushCredentials(RR,tPtr,now,peer->address(),_config,-1,false); + _announceMulticastGroupsTo(tPtr,peer->address(),_allMulticastGroups()); + } + return true; + } + } + } catch ( ... ) { + TRACE("gate() check failed for peer %s: unexpected exception",peer->address().toString().c_str()); + } + return false; +} + +void Network::clean() +{ + const uint64_t now = RR->node->now(); + Mutex::Lock _l(_lock); + + if (_destroyed) + return; + + { + Hashtable< MulticastGroup,uint64_t >::Iterator i(_multicastGroupsBehindMe); + MulticastGroup *mg = (MulticastGroup *)0; + uint64_t *ts = (uint64_t *)0; + while (i.next(mg,ts)) { + if ((now - *ts) > (ZT_MULTICAST_LIKE_EXPIRE * 2)) + _multicastGroupsBehindMe.erase(*mg); + } + } + + { + Address *a = (Address *)0; + Membership *m = (Membership *)0; + Hashtable::Iterator i(_memberships); + while (i.next(a,m)) { + if (!RR->topology->getPeerNoCache(*a)) + _memberships.erase(*a); + else m->clean(now,_config); + } + } +} + +void Network::learnBridgeRoute(const MAC &mac,const Address &addr) +{ + Mutex::Lock _l(_lock); + _remoteBridgeRoutes[mac] = addr; + + // Anti-DOS circuit breaker to prevent nodes from spamming us with absurd numbers of bridge routes + while (_remoteBridgeRoutes.size() > ZT_MAX_BRIDGE_ROUTES) { + Hashtable< Address,unsigned long > counts; + Address maxAddr; + unsigned long maxCount = 0; + + MAC *m = (MAC *)0; + Address *a = (Address *)0; + + // Find the address responsible for the most entries + { + Hashtable::Iterator i(_remoteBridgeRoutes); + while (i.next(m,a)) { + const unsigned long c = ++counts[*a]; + if (c > maxCount) { + maxCount = c; + maxAddr = *a; + } + } + } + + // Kill this address from our table, since it's most likely spamming us + { + Hashtable::Iterator i(_remoteBridgeRoutes); + while (i.next(m,a)) { + if (*a == maxAddr) + _remoteBridgeRoutes.erase(*m); + } + } + } +} + +void Network::learnBridgedMulticastGroup(void *tPtr,const MulticastGroup &mg,uint64_t now) +{ + Mutex::Lock _l(_lock); + const unsigned long tmp = (unsigned long)_multicastGroupsBehindMe.size(); + _multicastGroupsBehindMe.set(mg,now); + if (tmp != _multicastGroupsBehindMe.size()) + _sendUpdatesToMembers(tPtr,&mg); +} + +Membership::AddCredentialResult Network::addCredential(void *tPtr,const CertificateOfMembership &com) +{ + if (com.networkId() != _id) + return Membership::ADD_REJECTED; + const Address a(com.issuedTo()); + Mutex::Lock _l(_lock); + Membership &m = _membership(a); + const Membership::AddCredentialResult result = m.addCredential(RR,tPtr,_config,com); + if ((result == Membership::ADD_ACCEPTED_NEW)||(result == Membership::ADD_ACCEPTED_REDUNDANT)) { + m.pushCredentials(RR,tPtr,RR->node->now(),a,_config,-1,false); + RR->mc->addCredential(tPtr,com,true); + } + return result; +} + +Membership::AddCredentialResult Network::addCredential(void *tPtr,const Address &sentFrom,const Revocation &rev) +{ + if (rev.networkId() != _id) + return Membership::ADD_REJECTED; + + Mutex::Lock _l(_lock); + Membership &m = _membership(rev.target()); + + const Membership::AddCredentialResult result = m.addCredential(RR,tPtr,_config,rev); + + if ((result == Membership::ADD_ACCEPTED_NEW)&&(rev.fastPropagate())) { + Address *a = (Address *)0; + Membership *m = (Membership *)0; + Hashtable::Iterator i(_memberships); + while (i.next(a,m)) { + if ((*a != sentFrom)&&(*a != rev.signer())) { + Packet outp(*a,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS); + outp.append((uint8_t)0x00); // no COM + outp.append((uint16_t)0); // no capabilities + outp.append((uint16_t)0); // no tags + outp.append((uint16_t)1); // one revocation! + rev.serialize(outp); + outp.append((uint16_t)0); // no certificates of ownership + RR->sw->send(tPtr,outp,true); + } + } + } + + return result; +} + +void Network::destroy() +{ + Mutex::Lock _l(_lock); + _destroyed = true; +} + +ZT_VirtualNetworkStatus Network::_status() const +{ + // assumes _lock is locked + if (_portError) + return ZT_NETWORK_STATUS_PORT_ERROR; + switch(_netconfFailure) { + case NETCONF_FAILURE_ACCESS_DENIED: + return ZT_NETWORK_STATUS_ACCESS_DENIED; + case NETCONF_FAILURE_NOT_FOUND: + return ZT_NETWORK_STATUS_NOT_FOUND; + case NETCONF_FAILURE_NONE: + return ((_config) ? ZT_NETWORK_STATUS_OK : ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION); + default: + return ZT_NETWORK_STATUS_PORT_ERROR; + } +} + +void Network::_externalConfig(ZT_VirtualNetworkConfig *ec) const +{ + // assumes _lock is locked + ec->nwid = _id; + ec->mac = _mac.toInt(); + if (_config) + Utils::scopy(ec->name,sizeof(ec->name),_config.name); + else ec->name[0] = (char)0; + ec->status = _status(); + ec->type = (_config) ? (_config.isPrivate() ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC) : ZT_NETWORK_TYPE_PRIVATE; + ec->mtu = ZT_IF_MTU; + ec->physicalMtu = ZT_UDP_DEFAULT_PAYLOAD_MTU - (ZT_PACKET_IDX_PAYLOAD + 16); + ec->dhcp = 0; + std::vector
ab(_config.activeBridges()); + ec->bridge = ((_config.allowPassiveBridging())||(std::find(ab.begin(),ab.end(),RR->identity.address()) != ab.end())) ? 1 : 0; + ec->broadcastEnabled = (_config) ? (_config.enableBroadcast() ? 1 : 0) : 0; + ec->portError = _portError; + ec->netconfRevision = (_config) ? (unsigned long)_config.revision : 0; + + ec->assignedAddressCount = 0; + for(unsigned int i=0;iassignedAddresses[i]),&(_config.staticIps[i]),sizeof(struct sockaddr_storage)); + ++ec->assignedAddressCount; + } else { + memset(&(ec->assignedAddresses[i]),0,sizeof(struct sockaddr_storage)); + } + } + + ec->routeCount = 0; + for(unsigned int i=0;iroutes[i]),&(_config.routes[i]),sizeof(ZT_VirtualNetworkRoute)); + ++ec->routeCount; + } else { + memset(&(ec->routes[i]),0,sizeof(ZT_VirtualNetworkRoute)); + } + } +} + +void Network::_sendUpdatesToMembers(void *tPtr,const MulticastGroup *const newMulticastGroup) +{ + // Assumes _lock is locked + const uint64_t now = RR->node->now(); + + std::vector groups; + if (newMulticastGroup) + groups.push_back(*newMulticastGroup); + else groups = _allMulticastGroups(); + + if ((newMulticastGroup)||((now - _lastAnnouncedMulticastGroupsUpstream) >= ZT_MULTICAST_ANNOUNCE_PERIOD)) { + if (!newMulticastGroup) + _lastAnnouncedMulticastGroupsUpstream = now; + + // Announce multicast groups to upstream peers (roots, etc.) and also send + // them our COM so that MULTICAST_GATHER can be authenticated properly. + const std::vector
upstreams(RR->topology->upstreamAddresses()); + for(std::vector
::const_iterator a(upstreams.begin());a!=upstreams.end();++a) { + if (_config.com) { + Packet outp(*a,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS); + _config.com.serialize(outp); + outp.append((uint8_t)0x00); + outp.append((uint16_t)0); // no capabilities + outp.append((uint16_t)0); // no tags + outp.append((uint16_t)0); // no revocations + outp.append((uint16_t)0); // no certificates of ownership + RR->sw->send(tPtr,outp,true); + } + _announceMulticastGroupsTo(tPtr,*a,groups); + } + + // Also announce to controller, and send COM to simplify and generalize behavior even though in theory it does not need it + const Address c(controller()); + if ( (std::find(upstreams.begin(),upstreams.end(),c) == upstreams.end()) && (!_memberships.contains(c)) ) { + if (_config.com) { + Packet outp(c,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS); + _config.com.serialize(outp); + outp.append((uint8_t)0x00); + outp.append((uint16_t)0); // no capabilities + outp.append((uint16_t)0); // no tags + outp.append((uint16_t)0); // no revocations + outp.append((uint16_t)0); // no certificates of ownership + RR->sw->send(tPtr,outp,true); + } + _announceMulticastGroupsTo(tPtr,c,groups); + } + } + + // Make sure that all "network anchors" have Membership records so we will + // push multicasts to them. + const std::vector
anchors(_config.anchors()); + for(std::vector
::const_iterator a(anchors.begin());a!=anchors.end();++a) + _membership(*a); + + // Send credentials and multicast LIKEs to members, upstreams, and controller + { + Address *a = (Address *)0; + Membership *m = (Membership *)0; + Hashtable::Iterator i(_memberships); + while (i.next(a,m)) { + m->pushCredentials(RR,tPtr,now,*a,_config,-1,false); + if ( ( m->multicastLikeGate(now) || (newMulticastGroup) ) && (m->isAllowedOnNetwork(_config)) ) + _announceMulticastGroupsTo(tPtr,*a,groups); + } + } +} + +void Network::_announceMulticastGroupsTo(void *tPtr,const Address &peer,const std::vector &allMulticastGroups) +{ + // Assumes _lock is locked + Packet outp(peer,RR->identity.address(),Packet::VERB_MULTICAST_LIKE); + + for(std::vector::const_iterator mg(allMulticastGroups.begin());mg!=allMulticastGroups.end();++mg) { + if ((outp.size() + 24) >= ZT_PROTO_MAX_PACKET_LENGTH) { + outp.compress(); + RR->sw->send(tPtr,outp,true); + outp.reset(peer,RR->identity.address(),Packet::VERB_MULTICAST_LIKE); + } + + // network ID, MAC, ADI + outp.append((uint64_t)_id); + mg->mac().appendTo(outp); + outp.append((uint32_t)mg->adi()); + } + + if (outp.size() > ZT_PROTO_MIN_PACKET_LENGTH) { + outp.compress(); + RR->sw->send(tPtr,outp,true); + } +} + +std::vector Network::_allMulticastGroups() const +{ + // Assumes _lock is locked + std::vector mgs; + mgs.reserve(_myMulticastGroups.size() + _multicastGroupsBehindMe.size() + 1); + mgs.insert(mgs.end(),_myMulticastGroups.begin(),_myMulticastGroups.end()); + _multicastGroupsBehindMe.appendKeys(mgs); + if ((_config)&&(_config.enableBroadcast())) + mgs.push_back(Network::BROADCAST); + std::sort(mgs.begin(),mgs.end()); + mgs.erase(std::unique(mgs.begin(),mgs.end()),mgs.end()); + return mgs; +} + +Membership &Network::_membership(const Address &a) +{ + // assumes _lock is locked + return _memberships[a]; +} + +} // namespace ZeroTier diff --git a/node/Network.hpp b/node/Network.hpp new file mode 100644 index 0000000..faef0fe --- /dev/null +++ b/node/Network.hpp @@ -0,0 +1,422 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_NETWORK_HPP +#define ZT_NETWORK_HPP + +#include + +#include "../include/ZeroTierOne.h" + +#include +#include +#include +#include +#include + +#include "Constants.hpp" +#include "NonCopyable.hpp" +#include "Hashtable.hpp" +#include "Address.hpp" +#include "Mutex.hpp" +#include "SharedPtr.hpp" +#include "AtomicCounter.hpp" +#include "MulticastGroup.hpp" +#include "MAC.hpp" +#include "Dictionary.hpp" +#include "Multicaster.hpp" +#include "Membership.hpp" +#include "NetworkConfig.hpp" +#include "CertificateOfMembership.hpp" + +#define ZT_NETWORK_MAX_INCOMING_UPDATES 3 +#define ZT_NETWORK_MAX_UPDATE_CHUNKS ((ZT_NETWORKCONFIG_DICT_CAPACITY / 1024) + 1) + +namespace ZeroTier { + +class RuntimeEnvironment; +class Peer; + +/** + * A virtual LAN + */ +class Network : NonCopyable +{ + friend class SharedPtr; + +public: + /** + * Broadcast multicast group: ff:ff:ff:ff:ff:ff / 0 + */ + static const MulticastGroup BROADCAST; + + /** + * Compute primary controller device ID from network ID + */ + static inline Address controllerFor(uint64_t nwid) throw() { return Address(nwid >> 24); } + + /** + * Construct a new network + * + * Note that init() should be called immediately after the network is + * constructed to actually configure the port. + * + * @param renv Runtime environment + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param nwid Network ID + * @param uptr Arbitrary pointer used by externally-facing API (for user use) + */ + Network(const RuntimeEnvironment *renv,void *tPtr,uint64_t nwid,void *uptr); + + ~Network(); + + inline uint64_t id() const { return _id; } + inline Address controller() const { return Address(_id >> 24); } + inline bool multicastEnabled() const { return (_config.multicastLimit > 0); } + inline bool hasConfig() const { return (_config); } + inline uint64_t lastConfigUpdate() const throw() { return _lastConfigUpdate; } + inline ZT_VirtualNetworkStatus status() const { Mutex::Lock _l(_lock); return _status(); } + inline const NetworkConfig &config() const { return _config; } + inline const MAC &mac() const { return _mac; } + + /** + * Apply filters to an outgoing packet + * + * This applies filters from our network config and, if that doesn't match, + * our capabilities in ascending order of capability ID. Additional actions + * such as TEE may be taken, and credentials may be pushed, so this is not + * side-effect-free. It's basically step one in sending something over VL2. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param noTee If true, do not TEE anything anywhere (for two-pass filtering as done with multicast and bridging) + * @param ztSource Source ZeroTier address + * @param ztDest Destination ZeroTier address + * @param macSource Ethernet layer source address + * @param macDest Ethernet layer destination address + * @param frameData Ethernet frame data + * @param frameLen Ethernet frame payload length + * @param etherType 16-bit ethernet type ID + * @param vlanId 16-bit VLAN ID + * @return True if packet should be sent, false if dropped or redirected + */ + bool filterOutgoingPacket( + void *tPtr, + const bool noTee, + const Address &ztSource, + const Address &ztDest, + const MAC &macSource, + const MAC &macDest, + const uint8_t *frameData, + const unsigned int frameLen, + const unsigned int etherType, + const unsigned int vlanId); + + /** + * Apply filters to an incoming packet + * + * This applies filters from our network config and, if that doesn't match, + * the peer's capabilities in ascending order of capability ID. If there is + * a match certain actions may be taken such as sending a copy of the packet + * to a TEE or REDIRECT target. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param sourcePeer Source Peer + * @param ztDest Destination ZeroTier address + * @param macSource Ethernet layer source address + * @param macDest Ethernet layer destination address + * @param frameData Ethernet frame data + * @param frameLen Ethernet frame payload length + * @param etherType 16-bit ethernet type ID + * @param vlanId 16-bit VLAN ID + * @return 0 == drop, 1 == accept, 2 == accept even if bridged + */ + int filterIncomingPacket( + void *tPtr, + const SharedPtr &sourcePeer, + const Address &ztDest, + const MAC &macSource, + const MAC &macDest, + const uint8_t *frameData, + const unsigned int frameLen, + const unsigned int etherType, + const unsigned int vlanId); + + /** + * Check whether we are subscribed to a multicast group + * + * @param mg Multicast group + * @param includeBridgedGroups If true, also check groups we've learned via bridging + * @return True if this network endpoint / peer is a member + */ + bool subscribedToMulticastGroup(const MulticastGroup &mg,bool includeBridgedGroups) const; + + /** + * Subscribe to a multicast group + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param mg New multicast group + */ + void multicastSubscribe(void *tPtr,const MulticastGroup &mg); + + /** + * Unsubscribe from a multicast group + * + * @param mg Multicast group + */ + void multicastUnsubscribe(const MulticastGroup &mg); + + /** + * Handle an inbound network config chunk + * + * This is called from IncomingPacket to handle incoming network config + * chunks via OK(NETWORK_CONFIG_REQUEST) or NETWORK_CONFIG. It verifies + * each chunk and once assembled applies the configuration. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param packetId Packet ID or 0 if none (e.g. via cluster path) + * @param source Address of sender of chunk or NULL if none (e.g. via cluster path) + * @param chunk Buffer containing chunk + * @param ptr Index of chunk and related fields in packet + * @return Update ID if update was fully assembled and accepted or 0 otherwise + */ + uint64_t handleConfigChunk(void *tPtr,const uint64_t packetId,const Address &source,const Buffer &chunk,unsigned int ptr); + + /** + * Set network configuration + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param nconf Network configuration + * @param saveToDisk Save to disk? Used during loading, should usually be true otherwise. + * @return 0 == bad, 1 == accepted but duplicate/unchanged, 2 == accepted and new + */ + int setConfiguration(void *tPtr,const NetworkConfig &nconf,bool saveToDisk); + + /** + * Set netconf failure to 'access denied' -- called in IncomingPacket when controller reports this + */ + inline void setAccessDenied() + { + Mutex::Lock _l(_lock); + _netconfFailure = NETCONF_FAILURE_ACCESS_DENIED; + } + + /** + * Set netconf failure to 'not found' -- called by IncomingPacket when controller reports this + */ + inline void setNotFound() + { + Mutex::Lock _l(_lock); + _netconfFailure = NETCONF_FAILURE_NOT_FOUND; + } + + /** + * Causes this network to request an updated configuration from its master node now + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + */ + void requestConfiguration(void *tPtr); + + /** + * Determine whether this peer is permitted to communicate on this network + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param peer Peer to check + */ + bool gate(void *tPtr,const SharedPtr &peer); + + /** + * Do periodic cleanup and housekeeping tasks + */ + void clean(); + + /** + * Push state to members such as multicast group memberships and latest COM (if needed) + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + */ + inline void sendUpdatesToMembers(void *tPtr) + { + Mutex::Lock _l(_lock); + _sendUpdatesToMembers(tPtr,(const MulticastGroup *)0); + } + + /** + * Find the node on this network that has this MAC behind it (if any) + * + * @param mac MAC address + * @return ZeroTier address of bridge to this MAC + */ + inline Address findBridgeTo(const MAC &mac) const + { + Mutex::Lock _l(_lock); + const Address *const br = _remoteBridgeRoutes.get(mac); + return ((br) ? *br : Address()); + } + + /** + * Set a bridge route + * + * @param mac MAC address of destination + * @param addr Bridge this MAC is reachable behind + */ + void learnBridgeRoute(const MAC &mac,const Address &addr); + + /** + * Learn a multicast group that is bridged to our tap device + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param mg Multicast group + * @param now Current time + */ + void learnBridgedMulticastGroup(void *tPtr,const MulticastGroup &mg,uint64_t now); + + /** + * Validate a credential and learn it if it passes certificate and other checks + */ + Membership::AddCredentialResult addCredential(void *tPtr,const CertificateOfMembership &com); + + /** + * Validate a credential and learn it if it passes certificate and other checks + */ + inline Membership::AddCredentialResult addCredential(void *tPtr,const Capability &cap) + { + if (cap.networkId() != _id) + return Membership::ADD_REJECTED; + Mutex::Lock _l(_lock); + return _membership(cap.issuedTo()).addCredential(RR,tPtr,_config,cap); + } + + /** + * Validate a credential and learn it if it passes certificate and other checks + */ + inline Membership::AddCredentialResult addCredential(void *tPtr,const Tag &tag) + { + if (tag.networkId() != _id) + return Membership::ADD_REJECTED; + Mutex::Lock _l(_lock); + return _membership(tag.issuedTo()).addCredential(RR,tPtr,_config,tag); + } + + /** + * Validate a credential and learn it if it passes certificate and other checks + */ + Membership::AddCredentialResult addCredential(void *tPtr,const Address &sentFrom,const Revocation &rev); + + /** + * Validate a credential and learn it if it passes certificate and other checks + */ + inline Membership::AddCredentialResult addCredential(void *tPtr,const CertificateOfOwnership &coo) + { + if (coo.networkId() != _id) + return Membership::ADD_REJECTED; + Mutex::Lock _l(_lock); + return _membership(coo.issuedTo()).addCredential(RR,tPtr,_config,coo); + } + + /** + * Force push credentials (COM, etc.) to a peer now + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param to Destination peer address + * @param now Current time + */ + inline void pushCredentialsNow(void *tPtr,const Address &to,const uint64_t now) + { + Mutex::Lock _l(_lock); + _membership(to).pushCredentials(RR,tPtr,now,to,_config,-1,true); + } + + /** + * Destroy this network + * + * This sets the network to completely remove itself on delete. This also prevents the + * call of the normal port shutdown event on delete. + */ + void destroy(); + + /** + * Get this network's config for export via the ZT core API + * + * @param ec Buffer to fill with externally-visible network configuration + */ + inline void externalConfig(ZT_VirtualNetworkConfig *ec) const + { + Mutex::Lock _l(_lock); + _externalConfig(ec); + } + + /** + * @return Externally usable pointer-to-pointer exported via the core API + */ + inline void **userPtr() { return &_uPtr; } + +private: + ZT_VirtualNetworkStatus _status() const; + void _externalConfig(ZT_VirtualNetworkConfig *ec) const; // assumes _lock is locked + bool _gate(const SharedPtr &peer); + void _sendUpdatesToMembers(void *tPtr,const MulticastGroup *const newMulticastGroup); + void _announceMulticastGroupsTo(void *tPtr,const Address &peer,const std::vector &allMulticastGroups); + std::vector _allMulticastGroups() const; + Membership &_membership(const Address &a); + + const RuntimeEnvironment *const RR; + void *_uPtr; + const uint64_t _id; + uint64_t _lastAnnouncedMulticastGroupsUpstream; + MAC _mac; // local MAC address + bool _portInitialized; + + std::vector< MulticastGroup > _myMulticastGroups; // multicast groups that we belong to (according to tap) + Hashtable< MulticastGroup,uint64_t > _multicastGroupsBehindMe; // multicast groups that seem to be behind us and when we last saw them (if we are a bridge) + Hashtable< MAC,Address > _remoteBridgeRoutes; // remote addresses where given MACs are reachable (for tracking devices behind remote bridges) + + NetworkConfig _config; + uint64_t _lastConfigUpdate; + + struct _IncomingConfigChunk + { + _IncomingConfigChunk() { memset(this,0,sizeof(_IncomingConfigChunk)); } + uint64_t ts; + uint64_t updateId; + uint64_t haveChunkIds[ZT_NETWORK_MAX_UPDATE_CHUNKS]; + unsigned long haveChunks; + unsigned long haveBytes; + Dictionary data; + }; + _IncomingConfigChunk _incomingConfigChunks[ZT_NETWORK_MAX_INCOMING_UPDATES]; + + bool _destroyed; + + enum { + NETCONF_FAILURE_NONE, + NETCONF_FAILURE_ACCESS_DENIED, + NETCONF_FAILURE_NOT_FOUND, + NETCONF_FAILURE_INIT_FAILED + } _netconfFailure; + int _portError; // return value from port config callback + + Hashtable _memberships; + + Mutex _lock; + + AtomicCounter __refCount; +}; + +} // naemspace ZeroTier + +#endif diff --git a/node/NetworkConfig.cpp b/node/NetworkConfig.cpp new file mode 100644 index 0000000..fe7393e --- /dev/null +++ b/node/NetworkConfig.cpp @@ -0,0 +1,364 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include + +#include + +#include "NetworkConfig.hpp" + +namespace ZeroTier { + +bool NetworkConfig::toDictionary(Dictionary &d,bool includeLegacy) const +{ + Buffer *tmp = new Buffer(); + + try { + d.clear(); + + // Try to put the more human-readable fields first + + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_VERSION,(uint64_t)ZT_NETWORKCONFIG_VERSION)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,this->networkId)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,this->timestamp)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA,this->credentialTimeMaxDelta)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_REVISION,this->revision)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,this->issuedTo)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,this->flags)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,(uint64_t)this->multicastLimit)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)this->type)) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_NAME,this->name)) return false; + +#ifdef ZT_SUPPORT_OLD_STYLE_NETCONF + if (includeLegacy) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING_OLD,this->allowPassiveBridging())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD,this->enableBroadcast())) return false; + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD,this->isPrivate())) return false; + + std::string v4s; + for(unsigned int i=0;istaticIps[i].ss_family == AF_INET) { + if (v4s.length() > 0) + v4s.push_back(','); + v4s.append(this->staticIps[i].toString()); + } + } + if (v4s.length() > 0) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD,v4s.c_str())) return false; + } + std::string v6s; + for(unsigned int i=0;istaticIps[i].ss_family == AF_INET6) { + if (v6s.length() > 0) + v6s.push_back(','); + v6s.append(this->staticIps[i].toString()); + } + } + if (v6s.length() > 0) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD,v6s.c_str())) return false; + } + + std::string ets; + unsigned int et = 0; + ZT_VirtualNetworkRuleType lastrt = ZT_NETWORK_RULE_ACTION_ACCEPT; + for(unsigned int i=0;i 0) + ets.push_back(','); + char tmp2[16]; + Utils::snprintf(tmp2,sizeof(tmp2),"%x",et); + ets.append(tmp2); + } + et = 0; + } + lastrt = rt; + } + if (ets.length() > 0) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD,ets.c_str())) return false; + } + + if (this->com) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD,this->com.toString().c_str())) return false; + } + + std::string ab; + for(unsigned int i=0;ispecialistCount;++i) { + if ((this->specialists[i] & ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE) != 0) { + if (ab.length() > 0) + ab.push_back(','); + ab.append(Address(this->specialists[i]).toString().c_str()); + } + } + if (ab.length() > 0) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD,ab.c_str())) return false; + } + } +#endif // ZT_SUPPORT_OLD_STYLE_NETCONF + + // Then add binary blobs + + if (this->com) { + tmp->clear(); + this->com.serialize(*tmp); + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_COM,*tmp)) return false; + } + + tmp->clear(); + for(unsigned int i=0;icapabilityCount;++i) + this->capabilities[i].serialize(*tmp); + if (tmp->size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES,*tmp)) return false; + } + + tmp->clear(); + for(unsigned int i=0;itagCount;++i) + this->tags[i].serialize(*tmp); + if (tmp->size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_TAGS,*tmp)) return false; + } + + tmp->clear(); + for(unsigned int i=0;icertificateOfOwnershipCount;++i) + this->certificatesOfOwnership[i].serialize(*tmp); + if (tmp->size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP,*tmp)) return false; + } + + tmp->clear(); + for(unsigned int i=0;ispecialistCount;++i) + tmp->append((uint64_t)this->specialists[i]); + if (tmp->size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS,*tmp)) return false; + } + + tmp->clear(); + for(unsigned int i=0;irouteCount;++i) { + reinterpret_cast(&(this->routes[i].target))->serialize(*tmp); + reinterpret_cast(&(this->routes[i].via))->serialize(*tmp); + tmp->append((uint16_t)this->routes[i].flags); + tmp->append((uint16_t)this->routes[i].metric); + } + if (tmp->size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_ROUTES,*tmp)) return false; + } + + tmp->clear(); + for(unsigned int i=0;istaticIpCount;++i) + this->staticIps[i].serialize(*tmp); + if (tmp->size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS,*tmp)) return false; + } + + if (this->ruleCount) { + tmp->clear(); + Capability::serializeRules(*tmp,rules,ruleCount); + if (tmp->size()) { + if (!d.add(ZT_NETWORKCONFIG_DICT_KEY_RULES,*tmp)) return false; + } + } + + delete tmp; + } catch ( ... ) { + delete tmp; + throw; + } + + return true; +} + +bool NetworkConfig::fromDictionary(const Dictionary &d) +{ + Buffer *tmp = new Buffer(); + + try { + memset(this,0,sizeof(NetworkConfig)); + + // Fields that are always present, new or old + this->networkId = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID,0); + if (!this->networkId) { + delete tmp; + return false; + } + this->timestamp = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP,0); + this->credentialTimeMaxDelta = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA,0); + this->revision = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_REVISION,0); + this->issuedTo = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO,0); + if (!this->issuedTo) { + delete tmp; + return false; + } + this->multicastLimit = (unsigned int)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT,0); + d.get(ZT_NETWORKCONFIG_DICT_KEY_NAME,this->name,sizeof(this->name)); + + if (d.getUI(ZT_NETWORKCONFIG_DICT_KEY_VERSION,0) < 6) { + #ifdef ZT_SUPPORT_OLD_STYLE_NETCONF + char tmp2[1024]; + + // Decode legacy fields if version is old + if (d.getB(ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING_OLD)) + this->flags |= ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING; + if (d.getB(ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD)) + this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST; + this->flags |= ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; // always enable for old-style netconf + this->type = (d.getB(ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD,true)) ? ZT_NETWORK_TYPE_PRIVATE : ZT_NETWORK_TYPE_PUBLIC; + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD,tmp2,sizeof(tmp2)) > 0) { + char *saveptr = (char *)0; + for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { + if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) break; + InetAddress ip(f); + if (!ip.isNetwork()) + this->staticIps[this->staticIpCount++] = ip; + } + } + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD,tmp2,sizeof(tmp2)) > 0) { + char *saveptr = (char *)0; + for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { + if (this->staticIpCount >= ZT_MAX_ZT_ASSIGNED_ADDRESSES) break; + InetAddress ip(f); + if (!ip.isNetwork()) + this->staticIps[this->staticIpCount++] = ip; + } + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD,tmp2,sizeof(tmp2)) > 0) { + this->com.fromString(tmp2); + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD,tmp2,sizeof(tmp2)) > 0) { + char *saveptr = (char *)0; + for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { + unsigned int et = Utils::hexStrToUInt(f) & 0xffff; + if ((this->ruleCount + 2) > ZT_MAX_NETWORK_RULES) break; + if (et > 0) { + this->rules[this->ruleCount].t = (uint8_t)ZT_NETWORK_RULE_MATCH_ETHERTYPE; + this->rules[this->ruleCount].v.etherType = (uint16_t)et; + ++this->ruleCount; + } + this->rules[this->ruleCount++].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT; + } + } else { + this->rules[0].t = ZT_NETWORK_RULE_ACTION_ACCEPT; + this->ruleCount = 1; + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD,tmp2,sizeof(tmp2)) > 0) { + char *saveptr = (char *)0; + for(char *f=Utils::stok(tmp2,",",&saveptr);(f);f=Utils::stok((char *)0,",",&saveptr)) { + this->addSpecialist(Address(Utils::hexStrToU64(f)),ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE); + } + } + #else + delete tmp; + return false; + #endif // ZT_SUPPORT_OLD_STYLE_NETCONF + } else { + // Otherwise we can use the new fields + this->flags = d.getUI(ZT_NETWORKCONFIG_DICT_KEY_FLAGS,0); + this->type = (ZT_VirtualNetworkType)d.getUI(ZT_NETWORKCONFIG_DICT_KEY_TYPE,(uint64_t)ZT_NETWORK_TYPE_PRIVATE); + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_COM,*tmp)) + this->com.deserialize(*tmp,0); + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES,*tmp)) { + try { + unsigned int p = 0; + while (p < tmp->size()) { + Capability cap; + p += cap.deserialize(*tmp,p); + this->capabilities[this->capabilityCount++] = cap; + } + } catch ( ... ) {} + std::sort(&(this->capabilities[0]),&(this->capabilities[this->capabilityCount])); + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_TAGS,*tmp)) { + try { + unsigned int p = 0; + while (p < tmp->size()) { + Tag tag; + p += tag.deserialize(*tmp,p); + this->tags[this->tagCount++] = tag; + } + } catch ( ... ) {} + std::sort(&(this->tags[0]),&(this->tags[this->tagCount])); + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP,*tmp)) { + unsigned int p = 0; + while (p < tmp->size()) { + if (certificateOfOwnershipCount < ZT_MAX_CERTIFICATES_OF_OWNERSHIP) + p += certificatesOfOwnership[certificateOfOwnershipCount++].deserialize(*tmp,p); + else { + CertificateOfOwnership foo; + p += foo.deserialize(*tmp,p); + } + } + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS,*tmp)) { + unsigned int p = 0; + while ((p + 8) <= tmp->size()) { + if (specialistCount < ZT_MAX_NETWORK_SPECIALISTS) + this->specialists[this->specialistCount++] = tmp->at(p); + p += 8; + } + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_ROUTES,*tmp)) { + unsigned int p = 0; + while ((p < tmp->size())&&(routeCount < ZT_MAX_NETWORK_ROUTES)) { + p += reinterpret_cast(&(this->routes[this->routeCount].target))->deserialize(*tmp,p); + p += reinterpret_cast(&(this->routes[this->routeCount].via))->deserialize(*tmp,p); + this->routes[this->routeCount].flags = tmp->at(p); p += 2; + this->routes[this->routeCount].metric = tmp->at(p); p += 2; + ++this->routeCount; + } + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS,*tmp)) { + unsigned int p = 0; + while ((p < tmp->size())&&(staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) { + p += this->staticIps[this->staticIpCount++].deserialize(*tmp,p); + } + } + + if (d.get(ZT_NETWORKCONFIG_DICT_KEY_RULES,*tmp)) { + this->ruleCount = 0; + unsigned int p = 0; + Capability::deserializeRules(*tmp,p,this->rules,this->ruleCount,ZT_MAX_NETWORK_RULES); + } + } + + //printf("~~~\n%s\n~~~\n",d.data()); + //dump(); + //printf("~~~\n"); + + delete tmp; + return true; + } catch ( ... ) { + delete tmp; + return false; + } +} + +} // namespace ZeroTier diff --git a/node/NetworkConfig.hpp b/node/NetworkConfig.hpp new file mode 100644 index 0000000..85c2409 --- /dev/null +++ b/node/NetworkConfig.hpp @@ -0,0 +1,556 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_NETWORKCONFIG_HPP +#define ZT_NETWORKCONFIG_HPP + +#include +#include +#include + +#include +#include +#include + +#include "../include/ZeroTierOne.h" + +#include "Constants.hpp" +#include "Buffer.hpp" +#include "InetAddress.hpp" +#include "MulticastGroup.hpp" +#include "Address.hpp" +#include "CertificateOfMembership.hpp" +#include "CertificateOfOwnership.hpp" +#include "Capability.hpp" +#include "Tag.hpp" +#include "Dictionary.hpp" +#include "Identity.hpp" +#include "Utils.hpp" + +/** + * Default maximum time delta for COMs, tags, and capabilities + * + * The current value is two hours, providing ample time for a controller to + * experience fail-over, etc. + */ +#define ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA 7200000ULL + +/** + * Default minimum credential TTL and maxDelta for COM timestamps + * + * This is just slightly over three minutes and provides three retries for + * all currently online members to refresh. + */ +#define ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MIN_MAX_DELTA 185000ULL + +/** + * Flag: allow passive bridging (experimental) + */ +#define ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING 0x0000000000000001ULL + +/** + * Flag: enable broadcast + */ +#define ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST 0x0000000000000002ULL + +/** + * Flag: enable IPv6 NDP emulation for certain V6 address patterns + */ +#define ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION 0x0000000000000004ULL + +/** + * Flag: result of unrecognized MATCH entries in a rules table: match if set, no-match if clear + */ +#define ZT_NETWORKCONFIG_FLAG_RULES_RESULT_OF_UNSUPPORTED_MATCH 0x0000000000000008ULL + +/** + * Flag: disable frame compression + */ +#define ZT_NETWORKCONFIG_FLAG_DISABLE_COMPRESSION 0x0000000000000010ULL + +/** + * Device is an active bridge + */ +#define ZT_NETWORKCONFIG_SPECIALIST_TYPE_ACTIVE_BRIDGE 0x0000020000000000ULL + +/** + * Anchors are stable devices on this network that can cache multicast info, etc. + */ +#define ZT_NETWORKCONFIG_SPECIALIST_TYPE_ANCHOR 0x0000040000000000ULL + +/** + * Device can send CIRCUIT_TESTs for this network + */ +#define ZT_NETWORKCONFIG_SPECIALIST_TYPE_CIRCUIT_TESTER 0x0000080000000000ULL + +namespace ZeroTier { + +// Dictionary capacity needed for max size network config +#define ZT_NETWORKCONFIG_DICT_CAPACITY (1024 + (sizeof(ZT_VirtualNetworkRule) * ZT_MAX_NETWORK_RULES) + (sizeof(Capability) * ZT_MAX_NETWORK_CAPABILITIES) + (sizeof(Tag) * ZT_MAX_NETWORK_TAGS) + (sizeof(CertificateOfOwnership) * ZT_MAX_CERTIFICATES_OF_OWNERSHIP)) + +// Dictionary capacity needed for max size network meta-data +#define ZT_NETWORKCONFIG_METADATA_DICT_CAPACITY 1024 + +// Network config version +#define ZT_NETWORKCONFIG_VERSION 7 + +// Fields for meta-data sent with network config requests + +// Network config version +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_VERSION "v" +// Protocol version (see Packet.hpp) +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_PROTOCOL_VERSION "pv" +// Software vendor +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_VENDOR "vend" +// Software major version +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MAJOR_VERSION "majv" +// Software minor version +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_MINOR_VERSION "minv" +// Software revision +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_NODE_REVISION "revv" +// Rules engine revision +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_RULES_ENGINE_REV "revr" +// Maximum number of rules per network this node can accept +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_RULES "mr" +// Maximum number of capabilities this node can accept +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_CAPABILITIES "mc" +// Maximum number of rules per capability this node can accept +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_CAPABILITY_RULES "mcr" +// Maximum number of tags this node can accept +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_MAX_NETWORK_TAGS "mt" +// Network join authorization token (if any) +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_AUTH "a" +// Network configuration meta-data flags +#define ZT_NETWORKCONFIG_REQUEST_METADATA_KEY_FLAGS "f" + +// These dictionary keys are short so they don't take up much room. +// By convention we use upper case for binary blobs, but it doesn't really matter. + +// network config version +#define ZT_NETWORKCONFIG_DICT_KEY_VERSION "v" +// network ID +#define ZT_NETWORKCONFIG_DICT_KEY_NETWORK_ID "nwid" +// integer(hex) +#define ZT_NETWORKCONFIG_DICT_KEY_TIMESTAMP "ts" +// integer(hex) +#define ZT_NETWORKCONFIG_DICT_KEY_REVISION "r" +// address of member +#define ZT_NETWORKCONFIG_DICT_KEY_ISSUED_TO "id" +// flags(hex) +#define ZT_NETWORKCONFIG_DICT_KEY_FLAGS "f" +// integer(hex) +#define ZT_NETWORKCONFIG_DICT_KEY_MULTICAST_LIMIT "ml" +// network type (hex) +#define ZT_NETWORKCONFIG_DICT_KEY_TYPE "t" +// text +#define ZT_NETWORKCONFIG_DICT_KEY_NAME "n" +// credential time max delta in ms +#define ZT_NETWORKCONFIG_DICT_KEY_CREDENTIAL_TIME_MAX_DELTA "ctmd" +// binary serialized certificate of membership +#define ZT_NETWORKCONFIG_DICT_KEY_COM "C" +// specialists (binary array of uint64_t) +#define ZT_NETWORKCONFIG_DICT_KEY_SPECIALISTS "S" +// routes (binary blob) +#define ZT_NETWORKCONFIG_DICT_KEY_ROUTES "RT" +// static IPs (binary blob) +#define ZT_NETWORKCONFIG_DICT_KEY_STATIC_IPS "I" +// rules (binary blob) +#define ZT_NETWORKCONFIG_DICT_KEY_RULES "R" +// capabilities (binary blobs) +#define ZT_NETWORKCONFIG_DICT_KEY_CAPABILITIES "CAP" +// tags (binary blobs) +#define ZT_NETWORKCONFIG_DICT_KEY_TAGS "TAG" +// tags (binary blobs) +#define ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATES_OF_OWNERSHIP "COO" +// curve25519 signature +#define ZT_NETWORKCONFIG_DICT_KEY_SIGNATURE "C25519" + +// Legacy fields -- these are obsoleted but are included when older clients query + +// boolean (now a flag) +#define ZT_NETWORKCONFIG_DICT_KEY_ALLOW_PASSIVE_BRIDGING_OLD "pb" +// boolean (now a flag) +#define ZT_NETWORKCONFIG_DICT_KEY_ENABLE_BROADCAST_OLD "eb" +// IP/bits[,IP/bits,...] +// Note that IPs that end in all zeroes are routes with no assignment in them. +#define ZT_NETWORKCONFIG_DICT_KEY_IPV4_STATIC_OLD "v4s" +// IP/bits[,IP/bits,...] +// Note that IPs that end in all zeroes are routes with no assignment in them. +#define ZT_NETWORKCONFIG_DICT_KEY_IPV6_STATIC_OLD "v6s" +// 0/1 +#define ZT_NETWORKCONFIG_DICT_KEY_PRIVATE_OLD "p" +// integer(hex)[,integer(hex),...] +#define ZT_NETWORKCONFIG_DICT_KEY_ALLOWED_ETHERNET_TYPES_OLD "et" +// string-serialized CertificateOfMembership +#define ZT_NETWORKCONFIG_DICT_KEY_CERTIFICATE_OF_MEMBERSHIP_OLD "com" +// node[,node,...] +#define ZT_NETWORKCONFIG_DICT_KEY_ACTIVE_BRIDGES_OLD "ab" +// node;IP/port[,node;IP/port] +#define ZT_NETWORKCONFIG_DICT_KEY_RELAYS_OLD "rl" + +// End legacy fields + +/** + * Network configuration received from network controller nodes + * + * This is a memcpy()'able structure and is safe (in a crash sense) to modify + * without locks. + */ +class NetworkConfig +{ +public: + NetworkConfig() + { + memset(this,0,sizeof(NetworkConfig)); + } + + NetworkConfig(const NetworkConfig &nc) + { + memcpy(this,&nc,sizeof(NetworkConfig)); + } + + inline NetworkConfig &operator=(const NetworkConfig &nc) + { + memcpy(this,&nc,sizeof(NetworkConfig)); + return *this; + } + + /** + * Write this network config to a dictionary for transport + * + * @param d Dictionary + * @param includeLegacy If true, include legacy fields for old node versions + * @return True if dictionary was successfully created, false if e.g. overflow + */ + bool toDictionary(Dictionary &d,bool includeLegacy) const; + + /** + * Read this network config from a dictionary + * + * @param d Dictionary (non-const since it might be modified during parse, should not be used after call) + * @return True if dictionary was valid and network config successfully initialized + */ + bool fromDictionary(const Dictionary &d); + + /** + * @return True if passive bridging is allowed (experimental) + */ + inline bool allowPassiveBridging() const throw() { return ((this->flags & ZT_NETWORKCONFIG_FLAG_ALLOW_PASSIVE_BRIDGING) != 0); } + + /** + * @return True if broadcast (ff:ff:ff:ff:ff:ff) address should work on this network + */ + inline bool enableBroadcast() const throw() { return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_BROADCAST) != 0); } + + /** + * @return True if IPv6 NDP emulation should be allowed for certain "magic" IPv6 address patterns + */ + inline bool ndpEmulation() const throw() { return ((this->flags & ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION) != 0); } + + /** + * @return True if frames should not be compressed + */ + inline bool disableCompression() const throw() { return ((this->flags & ZT_NETWORKCONFIG_FLAG_DISABLE_COMPRESSION) != 0); } + + /** + * @return Network type is public (no access control) + */ + inline bool isPublic() const throw() { return (this->type == ZT_NETWORK_TYPE_PUBLIC); } + + /** + * @return Network type is private (certificate access control) + */ + inline bool isPrivate() const throw() { return (this->type == ZT_NETWORK_TYPE_PRIVATE); } + + /** + * @return ZeroTier addresses of devices on this network designated as active bridges + */ + inline std::vector
activeBridges() const + { + std::vector
r; + for(unsigned int i=0;i anchors() const + { + std::vector
r; + for(unsigned int i=0;i> 24) & 0xffffffffffULL)) + return true; + for(unsigned int i=0;i(&(routes[i].target))->toString().c_str()); + printf(" routes[i].via==%s\n",reinterpret_cast(&(routes[i].via))->toIpString().c_str()); + printf(" routes[i].flags==%.4x\n",(unsigned int)routes[i].flags); + printf(" routes[i].metric==%u\n",(unsigned int)routes[i].metric); + } + printf("staticIpCount==%u\n",staticIpCount); + for(unsigned int i=0;i. + */ + +#ifndef ZT_NETWORKCONFIGMASTER_HPP +#define ZT_NETWORKCONFIGMASTER_HPP + +#include + +#include "Constants.hpp" +#include "Dictionary.hpp" +#include "NetworkConfig.hpp" +#include "Revocation.hpp" +#include "Address.hpp" + +namespace ZeroTier { + +class Identity; +struct InetAddress; + +/** + * Interface for network controller implementations + */ +class NetworkController +{ +public: + enum ErrorCode + { + NC_ERROR_NONE = 0, + NC_ERROR_OBJECT_NOT_FOUND = 1, + NC_ERROR_ACCESS_DENIED = 2, + NC_ERROR_INTERNAL_SERVER_ERROR = 3 + }; + + /** + * Interface for sender used to send pushes and replies + */ + class Sender + { + public: + /** + * Send a configuration to a remote peer + * + * @param nwid Network ID + * @param requestPacketId Request packet ID to send OK(NETWORK_CONFIG_REQUEST) or 0 to send NETWORK_CONFIG (push) + * @param destination Destination peer Address + * @param nc Network configuration to send + * @param sendLegacyFormatConfig If true, send an old-format network config + */ + virtual void ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &destination,const NetworkConfig &nc,bool sendLegacyFormatConfig) = 0; + + /** + * Send revocation to a node + * + * @param destination Destination node address + * @param rev Revocation to send + */ + virtual void ncSendRevocation(const Address &destination,const Revocation &rev) = 0; + + /** + * Send a network configuration request error + * + * @param nwid Network ID + * @param requestPacketId Request packet ID or 0 if none + * @param destination Destination peer Address + * @param errorCode Error code + */ + virtual void ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode) = 0; + }; + + NetworkController() {} + virtual ~NetworkController() {} + + /** + * Called when this is added to a Node to initialize and supply info + * + * @param signingId Identity for signing of network configurations, certs, etc. + * @param sender Sender implementation for sending replies or config pushes + */ + virtual void init(const Identity &signingId,Sender *sender) = 0; + + /** + * Handle a network configuration request + * + * @param nwid 64-bit network ID + * @param fromAddr Originating wire address or null address if packet is not direct (or from self) + * @param requestPacketId Packet ID of request packet or 0 if not initiated by remote request + * @param identity ZeroTier identity of originating peer + * @param metaData Meta-data bundled with request (if any) + * @return Returns NETCONF_QUERY_OK if result 'nc' is valid, or an error code on error + */ + virtual void request( + uint64_t nwid, + const InetAddress &fromAddr, + uint64_t requestPacketId, + const Identity &identity, + const Dictionary &metaData) = 0; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Node.cpp b/node/Node.cpp new file mode 100644 index 0000000..2b3f799 --- /dev/null +++ b/node/Node.cpp @@ -0,0 +1,1143 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include +#include + +#include "../version.h" + +#include "Constants.hpp" +#include "Node.hpp" +#include "RuntimeEnvironment.hpp" +#include "NetworkController.hpp" +#include "Switch.hpp" +#include "Multicaster.hpp" +#include "Topology.hpp" +#include "Buffer.hpp" +#include "Packet.hpp" +#include "Address.hpp" +#include "Identity.hpp" +#include "SelfAwareness.hpp" +#include "Cluster.hpp" + +const struct sockaddr_storage ZT_SOCKADDR_NULL = {0}; + +namespace ZeroTier { + +/****************************************************************************/ +/* Public Node interface (C++, exposed via CAPI bindings) */ +/****************************************************************************/ + +Node::Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,uint64_t now) : + _RR(this), + RR(&_RR), + _uPtr(uptr), + _now(now), + _lastPingCheck(0), + _lastHousekeepingRun(0) +{ + if (callbacks->version != 0) + throw std::runtime_error("callbacks struct version mismatch"); + memcpy(&_cb,callbacks,sizeof(ZT_Node_Callbacks)); + + Utils::getSecureRandom((void *)_prngState,sizeof(_prngState)); + + _online = false; + + memset(_expectingRepliesToBucketPtr,0,sizeof(_expectingRepliesToBucketPtr)); + memset(_expectingRepliesTo,0,sizeof(_expectingRepliesTo)); + memset(_lastIdentityVerification,0,sizeof(_lastIdentityVerification)); + + std::string idtmp(dataStoreGet(tptr,"identity.secret")); + if ((!idtmp.length())||(!RR->identity.fromString(idtmp))||(!RR->identity.hasPrivate())) { + TRACE("identity.secret not found, generating..."); + RR->identity.generate(); + idtmp = RR->identity.toString(true); + if (!dataStorePut(tptr,"identity.secret",idtmp,true)) + throw std::runtime_error("unable to write identity.secret"); + } + RR->publicIdentityStr = RR->identity.toString(false); + RR->secretIdentityStr = RR->identity.toString(true); + idtmp = dataStoreGet(tptr,"identity.public"); + if (idtmp != RR->publicIdentityStr) { + if (!dataStorePut(tptr,"identity.public",RR->publicIdentityStr,false)) + throw std::runtime_error("unable to write identity.public"); + } + + try { + RR->sw = new Switch(RR); + RR->mc = new Multicaster(RR); + RR->topology = new Topology(RR,tptr); + RR->sa = new SelfAwareness(RR); + } catch ( ... ) { + delete RR->sa; + delete RR->topology; + delete RR->mc; + delete RR->sw; + throw; + } + + postEvent(tptr,ZT_EVENT_UP); +} + +Node::~Node() +{ + Mutex::Lock _l(_networks_m); + + _networks.clear(); // ensure that networks are destroyed before shutdow + + delete RR->sa; + delete RR->topology; + delete RR->mc; + delete RR->sw; + +#ifdef ZT_ENABLE_CLUSTER + delete RR->cluster; +#endif +} + +ZT_ResultCode Node::processWirePacket( + void *tptr, + uint64_t now, + const struct sockaddr_storage *localAddress, + const struct sockaddr_storage *remoteAddress, + const void *packetData, + unsigned int packetLength, + volatile uint64_t *nextBackgroundTaskDeadline) +{ + _now = now; + RR->sw->onRemotePacket(tptr,*(reinterpret_cast(localAddress)),*(reinterpret_cast(remoteAddress)),packetData,packetLength); + return ZT_RESULT_OK; +} + +ZT_ResultCode Node::processVirtualNetworkFrame( + void *tptr, + uint64_t now, + uint64_t nwid, + uint64_t sourceMac, + uint64_t destMac, + unsigned int etherType, + unsigned int vlanId, + const void *frameData, + unsigned int frameLength, + volatile uint64_t *nextBackgroundTaskDeadline) +{ + _now = now; + SharedPtr nw(this->network(nwid)); + if (nw) { + RR->sw->onLocalEthernet(tptr,nw,MAC(sourceMac),MAC(destMac),etherType,vlanId,frameData,frameLength); + return ZT_RESULT_OK; + } else return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; +} + +// Closure used to ping upstream and active/online peers +class _PingPeersThatNeedPing +{ +public: + _PingPeersThatNeedPing(const RuntimeEnvironment *renv,void *tPtr,Hashtable< Address,std::vector > &upstreamsToContact,uint64_t now) : + lastReceiveFromUpstream(0), + RR(renv), + _tPtr(tPtr), + _upstreamsToContact(upstreamsToContact), + _now(now), + _bestCurrentUpstream(RR->topology->getUpstreamPeer()) + { + } + + uint64_t lastReceiveFromUpstream; // tracks last time we got a packet from an 'upstream' peer like a root or a relay + + inline void operator()(Topology &t,const SharedPtr &p) + { + const std::vector *const upstreamStableEndpoints = _upstreamsToContact.get(p->address()); + if (upstreamStableEndpoints) { + bool contacted = false; + + // Upstreams must be pinged constantly over both IPv4 and IPv6 to allow + // them to perform three way handshake introductions for both stacks. + + if (!p->doPingAndKeepalive(_tPtr,_now,AF_INET)) { + for(unsigned long k=0,ptr=(unsigned long)RR->node->prng();k<(unsigned long)upstreamStableEndpoints->size();++k) { + const InetAddress &addr = (*upstreamStableEndpoints)[ptr++ % upstreamStableEndpoints->size()]; + if (addr.ss_family == AF_INET) { + p->sendHELLO(_tPtr,InetAddress(),addr,_now,0); + contacted = true; + break; + } + } + } else contacted = true; + if (!p->doPingAndKeepalive(_tPtr,_now,AF_INET6)) { + for(unsigned long k=0,ptr=(unsigned long)RR->node->prng();k<(unsigned long)upstreamStableEndpoints->size();++k) { + const InetAddress &addr = (*upstreamStableEndpoints)[ptr++ % upstreamStableEndpoints->size()]; + if (addr.ss_family == AF_INET6) { + p->sendHELLO(_tPtr,InetAddress(),addr,_now,0); + contacted = true; + break; + } + } + } else contacted = true; + + if ((!contacted)&&(_bestCurrentUpstream)) { + const SharedPtr up(_bestCurrentUpstream->getBestPath(_now,true)); + if (up) + p->sendHELLO(_tPtr,up->localAddress(),up->address(),_now,up->nextOutgoingCounter()); + } + + lastReceiveFromUpstream = std::max(p->lastReceive(),lastReceiveFromUpstream); + _upstreamsToContact.erase(p->address()); // erase from upstreams to contact so that we can WHOIS those that remain + } else if (p->isActive(_now)) { + p->doPingAndKeepalive(_tPtr,_now,-1); + } + } + +private: + const RuntimeEnvironment *RR; + void *_tPtr; + Hashtable< Address,std::vector > &_upstreamsToContact; + const uint64_t _now; + const SharedPtr _bestCurrentUpstream; +}; + +ZT_ResultCode Node::processBackgroundTasks(void *tptr,uint64_t now,volatile uint64_t *nextBackgroundTaskDeadline) +{ + _now = now; + Mutex::Lock bl(_backgroundTasksLock); + + unsigned long timeUntilNextPingCheck = ZT_PING_CHECK_INVERVAL; + const uint64_t timeSinceLastPingCheck = now - _lastPingCheck; + if (timeSinceLastPingCheck >= ZT_PING_CHECK_INVERVAL) { + try { + _lastPingCheck = now; + + // Get networks that need config without leaving mutex locked + std::vector< SharedPtr > needConfig; + { + Mutex::Lock _l(_networks_m); + for(std::vector< std::pair< uint64_t,SharedPtr > >::const_iterator n(_networks.begin());n!=_networks.end();++n) { + if (((now - n->second->lastConfigUpdate()) >= ZT_NETWORK_AUTOCONF_DELAY)||(!n->second->hasConfig())) + needConfig.push_back(n->second); + n->second->sendUpdatesToMembers(tptr); + } + } + for(std::vector< SharedPtr >::const_iterator n(needConfig.begin());n!=needConfig.end();++n) + (*n)->requestConfiguration(tptr); + + // Do pings and keepalives + Hashtable< Address,std::vector > upstreamsToContact; + RR->topology->getUpstreamsToContact(upstreamsToContact); + _PingPeersThatNeedPing pfunc(RR,tptr,upstreamsToContact,now); + RR->topology->eachPeer<_PingPeersThatNeedPing &>(pfunc); + + // Run WHOIS to create Peer for any upstreams we could not contact (including pending moon seeds) + Hashtable< Address,std::vector >::Iterator i(upstreamsToContact); + Address *upstreamAddress = (Address *)0; + std::vector *upstreamStableEndpoints = (std::vector *)0; + while (i.next(upstreamAddress,upstreamStableEndpoints)) + RR->sw->requestWhois(tptr,*upstreamAddress); + + // Update online status, post status change as event + const bool oldOnline = _online; + _online = (((now - pfunc.lastReceiveFromUpstream) < ZT_PEER_ACTIVITY_TIMEOUT)||(RR->topology->amRoot())); + if (oldOnline != _online) + postEvent(tptr,_online ? ZT_EVENT_ONLINE : ZT_EVENT_OFFLINE); + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } + } else { + timeUntilNextPingCheck -= (unsigned long)timeSinceLastPingCheck; + } + + if ((now - _lastHousekeepingRun) >= ZT_HOUSEKEEPING_PERIOD) { + try { + _lastHousekeepingRun = now; + RR->topology->clean(now); + RR->sa->clean(now); + RR->mc->clean(now); + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } + } + + try { +#ifdef ZT_ENABLE_CLUSTER + // If clustering is enabled we have to call cluster->doPeriodicTasks() very often, so we override normal timer deadline behavior + if (RR->cluster) { + RR->sw->doTimerTasks(tptr,now); + RR->cluster->doPeriodicTasks(); + *nextBackgroundTaskDeadline = now + ZT_CLUSTER_PERIODIC_TASK_PERIOD; // this is really short so just tick at this rate + } else { +#endif + *nextBackgroundTaskDeadline = now + (uint64_t)std::max(std::min(timeUntilNextPingCheck,RR->sw->doTimerTasks(tptr,now)),(unsigned long)ZT_CORE_TIMER_TASK_GRANULARITY); +#ifdef ZT_ENABLE_CLUSTER + } +#endif + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } + + return ZT_RESULT_OK; +} + +ZT_ResultCode Node::join(uint64_t nwid,void *uptr,void *tptr) +{ + Mutex::Lock _l(_networks_m); + SharedPtr nw = _network(nwid); + if(!nw) { + const std::pair< uint64_t,SharedPtr > nn(nwid,SharedPtr(new Network(RR,tptr,nwid,uptr))); + _networks.insert(std::upper_bound(_networks.begin(),_networks.end(),nn),nn); + } + return ZT_RESULT_OK; +} + +ZT_ResultCode Node::leave(uint64_t nwid,void **uptr,void *tptr) +{ + ZT_VirtualNetworkConfig ctmp; + std::vector< std::pair< uint64_t,SharedPtr > > newn; + void **nUserPtr = (void **)0; + Mutex::Lock _l(_networks_m); + + for(std::vector< std::pair< uint64_t,SharedPtr > >::const_iterator n(_networks.begin());n!=_networks.end();++n) { + if (n->first != nwid) { + newn.push_back(*n); + } else { + if (uptr) + *uptr = *n->second->userPtr(); + n->second->destroy(); + nUserPtr = n->second->userPtr(); + } + } + _networks.swap(newn); + + if (nUserPtr) + RR->node->configureVirtualNetworkPort(tptr,nwid,nUserPtr,ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY,&ctmp); + + return ZT_RESULT_OK; +} + +ZT_ResultCode Node::multicastSubscribe(void *tptr,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi) +{ + SharedPtr nw(this->network(nwid)); + if (nw) { + nw->multicastSubscribe(tptr,MulticastGroup(MAC(multicastGroup),(uint32_t)(multicastAdi & 0xffffffff))); + return ZT_RESULT_OK; + } else return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; +} + +ZT_ResultCode Node::multicastUnsubscribe(uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi) +{ + SharedPtr nw(this->network(nwid)); + if (nw) { + nw->multicastUnsubscribe(MulticastGroup(MAC(multicastGroup),(uint32_t)(multicastAdi & 0xffffffff))); + return ZT_RESULT_OK; + } else return ZT_RESULT_ERROR_NETWORK_NOT_FOUND; +} + +ZT_ResultCode Node::orbit(void *tptr,uint64_t moonWorldId,uint64_t moonSeed) +{ + RR->topology->addMoon(tptr,moonWorldId,Address(moonSeed)); + return ZT_RESULT_OK; +} + +ZT_ResultCode Node::deorbit(void *tptr,uint64_t moonWorldId) +{ + RR->topology->removeMoon(tptr,moonWorldId); + return ZT_RESULT_OK; +} + +uint64_t Node::address() const +{ + return RR->identity.address().toInt(); +} + +void Node::status(ZT_NodeStatus *status) const +{ + status->address = RR->identity.address().toInt(); + status->publicIdentity = RR->publicIdentityStr.c_str(); + status->secretIdentity = RR->secretIdentityStr.c_str(); + status->online = _online ? 1 : 0; +} + +ZT_PeerList *Node::peers() const +{ + std::vector< std::pair< Address,SharedPtr > > peers(RR->topology->allPeers()); + std::sort(peers.begin(),peers.end()); + + char *buf = (char *)::malloc(sizeof(ZT_PeerList) + (sizeof(ZT_Peer) * peers.size())); + if (!buf) + return (ZT_PeerList *)0; + ZT_PeerList *pl = (ZT_PeerList *)buf; + pl->peers = (ZT_Peer *)(buf + sizeof(ZT_PeerList)); + + pl->peerCount = 0; + for(std::vector< std::pair< Address,SharedPtr > >::iterator pi(peers.begin());pi!=peers.end();++pi) { + ZT_Peer *p = &(pl->peers[pl->peerCount++]); + p->address = pi->second->address().toInt(); + if (pi->second->remoteVersionKnown()) { + p->versionMajor = pi->second->remoteVersionMajor(); + p->versionMinor = pi->second->remoteVersionMinor(); + p->versionRev = pi->second->remoteVersionRevision(); + } else { + p->versionMajor = -1; + p->versionMinor = -1; + p->versionRev = -1; + } + p->latency = pi->second->latency(); + p->role = RR->topology->role(pi->second->identity().address()); + + std::vector< SharedPtr > paths(pi->second->paths(_now)); + SharedPtr bestp(pi->second->getBestPath(_now,false)); + p->pathCount = 0; + for(std::vector< SharedPtr >::iterator path(paths.begin());path!=paths.end();++path) { + memcpy(&(p->paths[p->pathCount].address),&((*path)->address()),sizeof(struct sockaddr_storage)); + p->paths[p->pathCount].lastSend = (*path)->lastOut(); + p->paths[p->pathCount].lastReceive = (*path)->lastIn(); + p->paths[p->pathCount].trustedPathId = RR->topology->getOutboundPathTrust((*path)->address()); + p->paths[p->pathCount].linkQuality = (int)(*path)->linkQuality(); + p->paths[p->pathCount].expired = 0; + p->paths[p->pathCount].preferred = ((*path) == bestp) ? 1 : 0; + ++p->pathCount; + } + } + + return pl; +} + +ZT_VirtualNetworkConfig *Node::networkConfig(uint64_t nwid) const +{ + Mutex::Lock _l(_networks_m); + SharedPtr nw = _network(nwid); + if(nw) { + ZT_VirtualNetworkConfig *nc = (ZT_VirtualNetworkConfig *)::malloc(sizeof(ZT_VirtualNetworkConfig)); + nw->externalConfig(nc); + return nc; + } + return (ZT_VirtualNetworkConfig *)0; +} + +ZT_VirtualNetworkList *Node::networks() const +{ + Mutex::Lock _l(_networks_m); + + char *buf = (char *)::malloc(sizeof(ZT_VirtualNetworkList) + (sizeof(ZT_VirtualNetworkConfig) * _networks.size())); + if (!buf) + return (ZT_VirtualNetworkList *)0; + ZT_VirtualNetworkList *nl = (ZT_VirtualNetworkList *)buf; + nl->networks = (ZT_VirtualNetworkConfig *)(buf + sizeof(ZT_VirtualNetworkList)); + + nl->networkCount = 0; + for(std::vector< std::pair< uint64_t,SharedPtr > >::const_iterator n(_networks.begin());n!=_networks.end();++n) + n->second->externalConfig(&(nl->networks[nl->networkCount++])); + + return nl; +} + +void Node::freeQueryResult(void *qr) +{ + if (qr) + ::free(qr); +} + +int Node::addLocalInterfaceAddress(const struct sockaddr_storage *addr) +{ + if (Path::isAddressValidForPath(*(reinterpret_cast(addr)))) { + Mutex::Lock _l(_directPaths_m); + if (std::find(_directPaths.begin(),_directPaths.end(),*(reinterpret_cast(addr))) == _directPaths.end()) { + _directPaths.push_back(*(reinterpret_cast(addr))); + return 1; + } + } + return 0; +} + +void Node::clearLocalInterfaceAddresses() +{ + Mutex::Lock _l(_directPaths_m); + _directPaths.clear(); +} + +int Node::sendUserMessage(void *tptr,uint64_t dest,uint64_t typeId,const void *data,unsigned int len) +{ + try { + if (RR->identity.address().toInt() != dest) { + Packet outp(Address(dest),RR->identity.address(),Packet::VERB_USER_MESSAGE); + outp.append(typeId); + outp.append(data,len); + outp.compress(); + RR->sw->send(tptr,outp,true); + return 1; + } + } catch ( ... ) {} + return 0; +} + +void Node::setNetconfMaster(void *networkControllerInstance) +{ + RR->localNetworkController = reinterpret_cast(networkControllerInstance); + RR->localNetworkController->init(RR->identity,this); +} + +ZT_ResultCode Node::circuitTestBegin(void *tptr,ZT_CircuitTest *test,void (*reportCallback)(ZT_Node *,ZT_CircuitTest *,const ZT_CircuitTestReport *)) +{ + if (test->hopCount > 0) { + try { + Packet outp(Address(),RR->identity.address(),Packet::VERB_CIRCUIT_TEST); + RR->identity.address().appendTo(outp); + outp.append((uint16_t)((test->reportAtEveryHop != 0) ? 0x03 : 0x02)); + outp.append((uint64_t)test->timestamp); + outp.append((uint64_t)test->testId); + outp.append((uint16_t)0); // originator credential length, updated later + if (test->credentialNetworkId) { + outp.append((uint8_t)0x01); + outp.append((uint64_t)test->credentialNetworkId); + outp.setAt(ZT_PACKET_IDX_PAYLOAD + 23,(uint16_t)9); + } + outp.append((uint16_t)0); + C25519::Signature sig(RR->identity.sign(reinterpret_cast(outp.data()) + ZT_PACKET_IDX_PAYLOAD,outp.size() - ZT_PACKET_IDX_PAYLOAD)); + outp.append((uint16_t)sig.size()); + outp.append(sig.data,(unsigned int)sig.size()); + outp.append((uint16_t)0); // originator doesn't need an extra credential, since it's the originator + for(unsigned int h=1;hhopCount;++h) { + outp.append((uint8_t)0); + outp.append((uint8_t)(test->hops[h].breadth & 0xff)); + for(unsigned int a=0;ahops[h].breadth;++a) + Address(test->hops[h].addresses[a]).appendTo(outp); + } + + for(unsigned int a=0;ahops[0].breadth;++a) { + outp.newInitializationVector(); + outp.setDestination(Address(test->hops[0].addresses[a])); + RR->sw->send(tptr,outp,true); + } + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; // probably indicates FIFO too big for packet + } + } + + { + test->_internalPtr = reinterpret_cast(reportCallback); + Mutex::Lock _l(_circuitTests_m); + if (std::find(_circuitTests.begin(),_circuitTests.end(),test) == _circuitTests.end()) + _circuitTests.push_back(test); + } + + return ZT_RESULT_OK; +} + +void Node::circuitTestEnd(ZT_CircuitTest *test) +{ + Mutex::Lock _l(_circuitTests_m); + for(;;) { + std::vector< ZT_CircuitTest * >::iterator ct(std::find(_circuitTests.begin(),_circuitTests.end(),test)); + if (ct == _circuitTests.end()) + break; + else _circuitTests.erase(ct); + } +} + +ZT_ResultCode Node::clusterInit( + unsigned int myId, + const struct sockaddr_storage *zeroTierPhysicalEndpoints, + unsigned int numZeroTierPhysicalEndpoints, + int x, + int y, + int z, + void (*sendFunction)(void *,unsigned int,const void *,unsigned int), + void *sendFunctionArg, + int (*addressToLocationFunction)(void *,const struct sockaddr_storage *,int *,int *,int *), + void *addressToLocationFunctionArg) +{ +#ifdef ZT_ENABLE_CLUSTER + if (RR->cluster) + return ZT_RESULT_ERROR_BAD_PARAMETER; + + std::vector eps; + for(unsigned int i=0;icluster = new Cluster(RR,myId,eps,x,y,z,sendFunction,sendFunctionArg,addressToLocationFunction,addressToLocationFunctionArg); + + return ZT_RESULT_OK; +#else + return ZT_RESULT_ERROR_UNSUPPORTED_OPERATION; +#endif +} + +ZT_ResultCode Node::clusterAddMember(unsigned int memberId) +{ +#ifdef ZT_ENABLE_CLUSTER + if (!RR->cluster) + return ZT_RESULT_ERROR_BAD_PARAMETER; + RR->cluster->addMember((uint16_t)memberId); + return ZT_RESULT_OK; +#else + return ZT_RESULT_ERROR_UNSUPPORTED_OPERATION; +#endif +} + +void Node::clusterRemoveMember(unsigned int memberId) +{ +#ifdef ZT_ENABLE_CLUSTER + if (RR->cluster) + RR->cluster->removeMember((uint16_t)memberId); +#endif +} + +void Node::clusterHandleIncomingMessage(const void *msg,unsigned int len) +{ +#ifdef ZT_ENABLE_CLUSTER + if (RR->cluster) + RR->cluster->handleIncomingStateMessage(msg,len); +#endif +} + +void Node::clusterStatus(ZT_ClusterStatus *cs) +{ + if (!cs) + return; +#ifdef ZT_ENABLE_CLUSTER + if (RR->cluster) + RR->cluster->status(*cs); + else +#endif + memset(cs,0,sizeof(ZT_ClusterStatus)); +} + +/****************************************************************************/ +/* Node methods used only within node/ */ +/****************************************************************************/ + +std::string Node::dataStoreGet(void *tPtr,const char *name) +{ + char buf[1024]; + std::string r; + unsigned long olen = 0; + do { + long n = _cb.dataStoreGetFunction(reinterpret_cast(this),_uPtr,tPtr,name,buf,sizeof(buf),(unsigned long)r.length(),&olen); + if (n <= 0) + return std::string(); + r.append(buf,n); + } while (r.length() < olen); + return r; +} + +bool Node::shouldUsePathForZeroTierTraffic(void *tPtr,const Address &ztaddr,const InetAddress &localAddress,const InetAddress &remoteAddress) +{ + if (!Path::isAddressValidForPath(remoteAddress)) + return false; + + if (RR->topology->isProhibitedEndpoint(ztaddr,remoteAddress)) + return false; + + { + Mutex::Lock _l(_networks_m); + for(std::vector< std::pair< uint64_t, SharedPtr > >::const_iterator i=_networks.begin();i!=_networks.end();++i) { + if (i->second->hasConfig()) { + for(unsigned int k=0;ksecond->config().staticIpCount;++k) { + if (i->second->config().staticIps[k].containsAddress(remoteAddress)) + return false; + } + } + } + } + + return ( (_cb.pathCheckFunction) ? (_cb.pathCheckFunction(reinterpret_cast(this),_uPtr,tPtr,ztaddr.toInt(),reinterpret_cast(&localAddress),reinterpret_cast(&remoteAddress)) != 0) : true); +} + +#ifdef ZT_TRACE +void Node::postTrace(const char *module,unsigned int line,const char *fmt,...) +{ + static Mutex traceLock; + + va_list ap; + char tmp1[1024],tmp2[1024],tmp3[256]; + + Mutex::Lock _l(traceLock); + + time_t now = (time_t)(_now / 1000ULL); +#ifdef __WINDOWS__ + ctime_s(tmp3,sizeof(tmp3),&now); + char *nowstr = tmp3; +#else + char *nowstr = ctime_r(&now,tmp3); +#endif + unsigned long nowstrlen = (unsigned long)strlen(nowstr); + if (nowstr[nowstrlen-1] == '\n') + nowstr[--nowstrlen] = (char)0; + if (nowstr[nowstrlen-1] == '\r') + nowstr[--nowstrlen] = (char)0; + + va_start(ap,fmt); + vsnprintf(tmp2,sizeof(tmp2),fmt,ap); + va_end(ap); + tmp2[sizeof(tmp2)-1] = (char)0; + + Utils::snprintf(tmp1,sizeof(tmp1),"[%s] %s:%u %s",nowstr,module,line,tmp2); + postEvent((void *)0,ZT_EVENT_TRACE,tmp1); +} +#endif // ZT_TRACE + +uint64_t Node::prng() +{ + // https://en.wikipedia.org/wiki/Xorshift#xorshift.2B + uint64_t x = _prngState[0]; + const uint64_t y = _prngState[1]; + _prngState[0] = y; + x ^= x << 23; + const uint64_t z = x ^ y ^ (x >> 17) ^ (y >> 26); + _prngState[1] = z; + return z + y; +} + +void Node::postCircuitTestReport(const ZT_CircuitTestReport *report) +{ + std::vector< ZT_CircuitTest * > toNotify; + { + Mutex::Lock _l(_circuitTests_m); + for(std::vector< ZT_CircuitTest * >::iterator i(_circuitTests.begin());i!=_circuitTests.end();++i) { + if ((*i)->testId == report->testId) + toNotify.push_back(*i); + } + } + for(std::vector< ZT_CircuitTest * >::iterator i(toNotify.begin());i!=toNotify.end();++i) + (reinterpret_cast((*i)->_internalPtr))(reinterpret_cast(this),*i,report); +} + +void Node::setTrustedPaths(const struct sockaddr_storage *networks,const uint64_t *ids,unsigned int count) +{ + RR->topology->setTrustedPaths(reinterpret_cast(networks),ids,count); +} + +World Node::planet() const +{ + return RR->topology->planet(); +} + +std::vector Node::moons() const +{ + return RR->topology->moons(); +} + +void Node::ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &destination,const NetworkConfig &nc,bool sendLegacyFormatConfig) +{ + if (destination == RR->identity.address()) { + SharedPtr n(network(nwid)); + if (!n) return; + n->setConfiguration((void *)0,nc,true); + } else { + Dictionary *dconf = new Dictionary(); + try { + if (nc.toDictionary(*dconf,sendLegacyFormatConfig)) { + uint64_t configUpdateId = prng(); + if (!configUpdateId) ++configUpdateId; + + const unsigned int totalSize = dconf->sizeBytes(); + unsigned int chunkIndex = 0; + while (chunkIndex < totalSize) { + const unsigned int chunkLen = std::min(totalSize - chunkIndex,(unsigned int)(ZT_UDP_DEFAULT_PAYLOAD_MTU - (ZT_PACKET_IDX_PAYLOAD + 256))); + Packet outp(destination,RR->identity.address(),(requestPacketId) ? Packet::VERB_OK : Packet::VERB_NETWORK_CONFIG); + if (requestPacketId) { + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(requestPacketId); + } + + const unsigned int sigStart = outp.size(); + outp.append(nwid); + outp.append((uint16_t)chunkLen); + outp.append((const void *)(dconf->data() + chunkIndex),chunkLen); + + outp.append((uint8_t)0); // no flags + outp.append((uint64_t)configUpdateId); + outp.append((uint32_t)totalSize); + outp.append((uint32_t)chunkIndex); + + C25519::Signature sig(RR->identity.sign(reinterpret_cast(outp.data()) + sigStart,outp.size() - sigStart)); + outp.append((uint8_t)1); + outp.append((uint16_t)ZT_C25519_SIGNATURE_LEN); + outp.append(sig.data,ZT_C25519_SIGNATURE_LEN); + + outp.compress(); + RR->sw->send((void *)0,outp,true); + chunkIndex += chunkLen; + } + } + delete dconf; + } catch ( ... ) { + delete dconf; + throw; + } + } +} + +void Node::ncSendRevocation(const Address &destination,const Revocation &rev) +{ + if (destination == RR->identity.address()) { + SharedPtr n(network(rev.networkId())); + if (!n) return; + n->addCredential((void *)0,RR->identity.address(),rev); + } else { + Packet outp(destination,RR->identity.address(),Packet::VERB_NETWORK_CREDENTIALS); + outp.append((uint8_t)0x00); + outp.append((uint16_t)0); + outp.append((uint16_t)0); + outp.append((uint16_t)1); + rev.serialize(outp); + outp.append((uint16_t)0); + RR->sw->send((void *)0,outp,true); + } +} + +void Node::ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode) +{ + if (destination == RR->identity.address()) { + SharedPtr n(network(nwid)); + if (!n) return; + switch(errorCode) { + case NetworkController::NC_ERROR_OBJECT_NOT_FOUND: + case NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR: + n->setNotFound(); + break; + case NetworkController::NC_ERROR_ACCESS_DENIED: + n->setAccessDenied(); + break; + + default: break; + } + } else if (requestPacketId) { + Packet outp(destination,RR->identity.address(),Packet::VERB_ERROR); + outp.append((unsigned char)Packet::VERB_NETWORK_CONFIG_REQUEST); + outp.append(requestPacketId); + switch(errorCode) { + //case NetworkController::NC_ERROR_OBJECT_NOT_FOUND: + //case NetworkController::NC_ERROR_INTERNAL_SERVER_ERROR: + default: + outp.append((unsigned char)Packet::ERROR_OBJ_NOT_FOUND); + break; + case NetworkController::NC_ERROR_ACCESS_DENIED: + outp.append((unsigned char)Packet::ERROR_NETWORK_ACCESS_DENIED_); + break; + } + outp.append(nwid); + RR->sw->send((void *)0,outp,true); + } // else we can't send an ERROR() in response to nothing, so discard +} + +} // namespace ZeroTier + +/****************************************************************************/ +/* CAPI bindings */ +/****************************************************************************/ + +extern "C" { + +enum ZT_ResultCode ZT_Node_new(ZT_Node **node,void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,uint64_t now) +{ + *node = (ZT_Node *)0; + try { + *node = reinterpret_cast(new ZeroTier::Node(uptr,tptr,callbacks,now)); + return ZT_RESULT_OK; + } catch (std::bad_alloc &exc) { + return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY; + } catch (std::runtime_error &exc) { + return ZT_RESULT_FATAL_ERROR_DATA_STORE_FAILED; + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } +} + +void ZT_Node_delete(ZT_Node *node) +{ + try { + delete (reinterpret_cast(node)); + } catch ( ... ) {} +} + +enum ZT_ResultCode ZT_Node_processWirePacket( + ZT_Node *node, + void *tptr, + uint64_t now, + const struct sockaddr_storage *localAddress, + const struct sockaddr_storage *remoteAddress, + const void *packetData, + unsigned int packetLength, + volatile uint64_t *nextBackgroundTaskDeadline) +{ + try { + return reinterpret_cast(node)->processWirePacket(tptr,now,localAddress,remoteAddress,packetData,packetLength,nextBackgroundTaskDeadline); + } catch (std::bad_alloc &exc) { + return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY; + } catch ( ... ) { + return ZT_RESULT_OK; // "OK" since invalid packets are simply dropped, but the system is still up + } +} + +enum ZT_ResultCode ZT_Node_processVirtualNetworkFrame( + ZT_Node *node, + void *tptr, + uint64_t now, + uint64_t nwid, + uint64_t sourceMac, + uint64_t destMac, + unsigned int etherType, + unsigned int vlanId, + const void *frameData, + unsigned int frameLength, + volatile uint64_t *nextBackgroundTaskDeadline) +{ + try { + return reinterpret_cast(node)->processVirtualNetworkFrame(tptr,now,nwid,sourceMac,destMac,etherType,vlanId,frameData,frameLength,nextBackgroundTaskDeadline); + } catch (std::bad_alloc &exc) { + return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY; + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } +} + +enum ZT_ResultCode ZT_Node_processBackgroundTasks(ZT_Node *node,void *tptr,uint64_t now,volatile uint64_t *nextBackgroundTaskDeadline) +{ + try { + return reinterpret_cast(node)->processBackgroundTasks(tptr,now,nextBackgroundTaskDeadline); + } catch (std::bad_alloc &exc) { + return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY; + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } +} + +enum ZT_ResultCode ZT_Node_join(ZT_Node *node,uint64_t nwid,void *uptr,void *tptr) +{ + try { + return reinterpret_cast(node)->join(nwid,uptr,tptr); + } catch (std::bad_alloc &exc) { + return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY; + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } +} + +enum ZT_ResultCode ZT_Node_leave(ZT_Node *node,uint64_t nwid,void **uptr,void *tptr) +{ + try { + return reinterpret_cast(node)->leave(nwid,uptr,tptr); + } catch (std::bad_alloc &exc) { + return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY; + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } +} + +enum ZT_ResultCode ZT_Node_multicastSubscribe(ZT_Node *node,void *tptr,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi) +{ + try { + return reinterpret_cast(node)->multicastSubscribe(tptr,nwid,multicastGroup,multicastAdi); + } catch (std::bad_alloc &exc) { + return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY; + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } +} + +enum ZT_ResultCode ZT_Node_multicastUnsubscribe(ZT_Node *node,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi) +{ + try { + return reinterpret_cast(node)->multicastUnsubscribe(nwid,multicastGroup,multicastAdi); + } catch (std::bad_alloc &exc) { + return ZT_RESULT_FATAL_ERROR_OUT_OF_MEMORY; + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } +} + +enum ZT_ResultCode ZT_Node_orbit(ZT_Node *node,void *tptr,uint64_t moonWorldId,uint64_t moonSeed) +{ + try { + return reinterpret_cast(node)->orbit(tptr,moonWorldId,moonSeed); + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } +} + +ZT_ResultCode ZT_Node_deorbit(ZT_Node *node,void *tptr,uint64_t moonWorldId) +{ + try { + return reinterpret_cast(node)->deorbit(tptr,moonWorldId); + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } +} + +uint64_t ZT_Node_address(ZT_Node *node) +{ + return reinterpret_cast(node)->address(); +} + +void ZT_Node_status(ZT_Node *node,ZT_NodeStatus *status) +{ + try { + reinterpret_cast(node)->status(status); + } catch ( ... ) {} +} + +ZT_PeerList *ZT_Node_peers(ZT_Node *node) +{ + try { + return reinterpret_cast(node)->peers(); + } catch ( ... ) { + return (ZT_PeerList *)0; + } +} + +ZT_VirtualNetworkConfig *ZT_Node_networkConfig(ZT_Node *node,uint64_t nwid) +{ + try { + return reinterpret_cast(node)->networkConfig(nwid); + } catch ( ... ) { + return (ZT_VirtualNetworkConfig *)0; + } +} + +ZT_VirtualNetworkList *ZT_Node_networks(ZT_Node *node) +{ + try { + return reinterpret_cast(node)->networks(); + } catch ( ... ) { + return (ZT_VirtualNetworkList *)0; + } +} + +void ZT_Node_freeQueryResult(ZT_Node *node,void *qr) +{ + try { + reinterpret_cast(node)->freeQueryResult(qr); + } catch ( ... ) {} +} + +int ZT_Node_addLocalInterfaceAddress(ZT_Node *node,const struct sockaddr_storage *addr) +{ + try { + return reinterpret_cast(node)->addLocalInterfaceAddress(addr); + } catch ( ... ) { + return 0; + } +} + +void ZT_Node_clearLocalInterfaceAddresses(ZT_Node *node) +{ + try { + reinterpret_cast(node)->clearLocalInterfaceAddresses(); + } catch ( ... ) {} +} + +int ZT_Node_sendUserMessage(ZT_Node *node,void *tptr,uint64_t dest,uint64_t typeId,const void *data,unsigned int len) +{ + try { + return reinterpret_cast(node)->sendUserMessage(tptr,dest,typeId,data,len); + } catch ( ... ) { + return 0; + } +} + +void ZT_Node_setNetconfMaster(ZT_Node *node,void *networkControllerInstance) +{ + try { + reinterpret_cast(node)->setNetconfMaster(networkControllerInstance); + } catch ( ... ) {} +} + +enum ZT_ResultCode ZT_Node_circuitTestBegin(ZT_Node *node,void *tptr,ZT_CircuitTest *test,void (*reportCallback)(ZT_Node *,ZT_CircuitTest *,const ZT_CircuitTestReport *)) +{ + try { + return reinterpret_cast(node)->circuitTestBegin(tptr,test,reportCallback); + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } +} + +void ZT_Node_circuitTestEnd(ZT_Node *node,ZT_CircuitTest *test) +{ + try { + reinterpret_cast(node)->circuitTestEnd(test); + } catch ( ... ) {} +} + +enum ZT_ResultCode ZT_Node_clusterInit( + ZT_Node *node, + unsigned int myId, + const struct sockaddr_storage *zeroTierPhysicalEndpoints, + unsigned int numZeroTierPhysicalEndpoints, + int x, + int y, + int z, + void (*sendFunction)(void *,unsigned int,const void *,unsigned int), + void *sendFunctionArg, + int (*addressToLocationFunction)(void *,const struct sockaddr_storage *,int *,int *,int *), + void *addressToLocationFunctionArg) +{ + try { + return reinterpret_cast(node)->clusterInit(myId,zeroTierPhysicalEndpoints,numZeroTierPhysicalEndpoints,x,y,z,sendFunction,sendFunctionArg,addressToLocationFunction,addressToLocationFunctionArg); + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } +} + +enum ZT_ResultCode ZT_Node_clusterAddMember(ZT_Node *node,unsigned int memberId) +{ + try { + return reinterpret_cast(node)->clusterAddMember(memberId); + } catch ( ... ) { + return ZT_RESULT_FATAL_ERROR_INTERNAL; + } +} + +void ZT_Node_clusterRemoveMember(ZT_Node *node,unsigned int memberId) +{ + try { + reinterpret_cast(node)->clusterRemoveMember(memberId); + } catch ( ... ) {} +} + +void ZT_Node_clusterHandleIncomingMessage(ZT_Node *node,const void *msg,unsigned int len) +{ + try { + reinterpret_cast(node)->clusterHandleIncomingMessage(msg,len); + } catch ( ... ) {} +} + +void ZT_Node_clusterStatus(ZT_Node *node,ZT_ClusterStatus *cs) +{ + try { + reinterpret_cast(node)->clusterStatus(cs); + } catch ( ... ) {} +} + +void ZT_Node_setTrustedPaths(ZT_Node *node,const struct sockaddr_storage *networks,const uint64_t *ids,unsigned int count) +{ + try { + reinterpret_cast(node)->setTrustedPaths(networks,ids,count); + } catch ( ... ) {} +} + +void ZT_version(int *major,int *minor,int *revision) +{ + if (major) *major = ZEROTIER_ONE_VERSION_MAJOR; + if (minor) *minor = ZEROTIER_ONE_VERSION_MINOR; + if (revision) *revision = ZEROTIER_ONE_VERSION_REVISION; +} + +} // extern "C" diff --git a/node/Node.hpp b/node/Node.hpp new file mode 100644 index 0000000..d25a619 --- /dev/null +++ b/node/Node.hpp @@ -0,0 +1,321 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_NODE_HPP +#define ZT_NODE_HPP + +#include +#include +#include + +#include +#include + +#include "Constants.hpp" + +#include "../include/ZeroTierOne.h" + +#include "RuntimeEnvironment.hpp" +#include "InetAddress.hpp" +#include "Mutex.hpp" +#include "MAC.hpp" +#include "Network.hpp" +#include "Path.hpp" +#include "Salsa20.hpp" +#include "NetworkController.hpp" + +#undef TRACE +#ifdef ZT_TRACE +#define TRACE(f,...) RR->node->postTrace(__FILE__,__LINE__,f,##__VA_ARGS__) +#else +#define TRACE(f,...) {} +#endif + +// Bit mask for "expecting reply" hash +#define ZT_EXPECTING_REPLIES_BUCKET_MASK1 255 +#define ZT_EXPECTING_REPLIES_BUCKET_MASK2 31 + +namespace ZeroTier { + +class World; + +/** + * Implementation of Node object as defined in CAPI + * + * The pointer returned by ZT_Node_new() is an instance of this class. + */ +class Node : public NetworkController::Sender +{ +public: + Node(void *uptr,void *tptr,const struct ZT_Node_Callbacks *callbacks,uint64_t now); + virtual ~Node(); + + // Get rid of alignment warnings on 32-bit Windows and possibly improve performance +#ifdef __WINDOWS__ + void * operator new(size_t i) { return _mm_malloc(i,16); } + void operator delete(void* p) { _mm_free(p); } +#endif + + // Public API Functions ---------------------------------------------------- + + ZT_ResultCode processWirePacket( + void *tptr, + uint64_t now, + const struct sockaddr_storage *localAddress, + const struct sockaddr_storage *remoteAddress, + const void *packetData, + unsigned int packetLength, + volatile uint64_t *nextBackgroundTaskDeadline); + ZT_ResultCode processVirtualNetworkFrame( + void *tptr, + uint64_t now, + uint64_t nwid, + uint64_t sourceMac, + uint64_t destMac, + unsigned int etherType, + unsigned int vlanId, + const void *frameData, + unsigned int frameLength, + volatile uint64_t *nextBackgroundTaskDeadline); + ZT_ResultCode processBackgroundTasks(void *tptr,uint64_t now,volatile uint64_t *nextBackgroundTaskDeadline); + ZT_ResultCode join(uint64_t nwid,void *uptr,void *tptr); + ZT_ResultCode leave(uint64_t nwid,void **uptr,void *tptr); + ZT_ResultCode multicastSubscribe(void *tptr,uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi); + ZT_ResultCode multicastUnsubscribe(uint64_t nwid,uint64_t multicastGroup,unsigned long multicastAdi); + ZT_ResultCode orbit(void *tptr,uint64_t moonWorldId,uint64_t moonSeed); + ZT_ResultCode deorbit(void *tptr,uint64_t moonWorldId); + uint64_t address() const; + void status(ZT_NodeStatus *status) const; + ZT_PeerList *peers() const; + ZT_VirtualNetworkConfig *networkConfig(uint64_t nwid) const; + ZT_VirtualNetworkList *networks() const; + void freeQueryResult(void *qr); + int addLocalInterfaceAddress(const struct sockaddr_storage *addr); + void clearLocalInterfaceAddresses(); + int sendUserMessage(void *tptr,uint64_t dest,uint64_t typeId,const void *data,unsigned int len); + void setNetconfMaster(void *networkControllerInstance); + ZT_ResultCode circuitTestBegin(void *tptr,ZT_CircuitTest *test,void (*reportCallback)(ZT_Node *,ZT_CircuitTest *,const ZT_CircuitTestReport *)); + void circuitTestEnd(ZT_CircuitTest *test); + ZT_ResultCode clusterInit( + unsigned int myId, + const struct sockaddr_storage *zeroTierPhysicalEndpoints, + unsigned int numZeroTierPhysicalEndpoints, + int x, + int y, + int z, + void (*sendFunction)(void *,unsigned int,const void *,unsigned int), + void *sendFunctionArg, + int (*addressToLocationFunction)(void *,const struct sockaddr_storage *,int *,int *,int *), + void *addressToLocationFunctionArg); + ZT_ResultCode clusterAddMember(unsigned int memberId); + void clusterRemoveMember(unsigned int memberId); + void clusterHandleIncomingMessage(const void *msg,unsigned int len); + void clusterStatus(ZT_ClusterStatus *cs); + + // Internal functions ------------------------------------------------------ + + inline uint64_t now() const throw() { return _now; } + + inline bool putPacket(void *tPtr,const InetAddress &localAddress,const InetAddress &addr,const void *data,unsigned int len,unsigned int ttl = 0) + { + return (_cb.wirePacketSendFunction( + reinterpret_cast(this), + _uPtr, + tPtr, + reinterpret_cast(&localAddress), + reinterpret_cast(&addr), + data, + len, + ttl) == 0); + } + + inline void putFrame(void *tPtr,uint64_t nwid,void **nuptr,const MAC &source,const MAC &dest,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) + { + _cb.virtualNetworkFrameFunction( + reinterpret_cast(this), + _uPtr, + tPtr, + nwid, + nuptr, + source.toInt(), + dest.toInt(), + etherType, + vlanId, + data, + len); + } + + inline SharedPtr network(uint64_t nwid) const + { + Mutex::Lock _l(_networks_m); + return _network(nwid); + } + + inline bool belongsToNetwork(uint64_t nwid) const + { + Mutex::Lock _l(_networks_m); + for(std::vector< std::pair< uint64_t, SharedPtr > >::const_iterator i=_networks.begin();i!=_networks.end();++i) { + if (i->first == nwid) + return true; + } + return false; + } + + inline std::vector< SharedPtr > allNetworks() const + { + std::vector< SharedPtr > nw; + Mutex::Lock _l(_networks_m); + nw.reserve(_networks.size()); + for(std::vector< std::pair< uint64_t, SharedPtr > >::const_iterator i=_networks.begin();i!=_networks.end();++i) + nw.push_back(i->second); + return nw; + } + + inline std::vector directPaths() const + { + Mutex::Lock _l(_directPaths_m); + return _directPaths; + } + + inline bool dataStorePut(void *tPtr,const char *name,const void *data,unsigned int len,bool secure) { return (_cb.dataStorePutFunction(reinterpret_cast(this),_uPtr,tPtr,name,data,len,(int)secure) == 0); } + inline bool dataStorePut(void *tPtr,const char *name,const std::string &data,bool secure) { return dataStorePut(tPtr,name,(const void *)data.data(),(unsigned int)data.length(),secure); } + inline void dataStoreDelete(void *tPtr,const char *name) { _cb.dataStorePutFunction(reinterpret_cast(this),_uPtr,tPtr,name,(const void *)0,0,0); } + std::string dataStoreGet(void *tPtr,const char *name); + + inline void postEvent(void *tPtr,ZT_Event ev,const void *md = (const void *)0) { _cb.eventCallback(reinterpret_cast(this),_uPtr,tPtr,ev,md); } + + inline int configureVirtualNetworkPort(void *tPtr,uint64_t nwid,void **nuptr,ZT_VirtualNetworkConfigOperation op,const ZT_VirtualNetworkConfig *nc) { return _cb.virtualNetworkConfigFunction(reinterpret_cast(this),_uPtr,tPtr,nwid,nuptr,op,nc); } + + inline bool online() const throw() { return _online; } + +#ifdef ZT_TRACE + void postTrace(const char *module,unsigned int line,const char *fmt,...); +#endif + + bool shouldUsePathForZeroTierTraffic(void *tPtr,const Address &ztaddr,const InetAddress &localAddress,const InetAddress &remoteAddress); + inline bool externalPathLookup(void *tPtr,const Address &ztaddr,int family,InetAddress &addr) { return ( (_cb.pathLookupFunction) ? (_cb.pathLookupFunction(reinterpret_cast(this),_uPtr,tPtr,ztaddr.toInt(),family,reinterpret_cast(&addr)) != 0) : false ); } + + uint64_t prng(); + void postCircuitTestReport(const ZT_CircuitTestReport *report); + void setTrustedPaths(const struct sockaddr_storage *networks,const uint64_t *ids,unsigned int count); + + World planet() const; + std::vector moons() const; + + /** + * Register that we are expecting a reply to a packet ID + * + * This only uses the most significant bits of the packet ID, both to save space + * and to avoid using the higher bits that can be modified during armor() to + * mask against the packet send counter used for QoS detection. + * + * @param packetId Packet ID to expect reply to + */ + inline void expectReplyTo(const uint64_t packetId) + { + const unsigned long pid2 = (unsigned long)(packetId >> 32); + const unsigned long bucket = (unsigned long)(pid2 & ZT_EXPECTING_REPLIES_BUCKET_MASK1); + _expectingRepliesTo[bucket][_expectingRepliesToBucketPtr[bucket]++ & ZT_EXPECTING_REPLIES_BUCKET_MASK2] = (uint32_t)pid2; + } + + /** + * Check whether a given packet ID is something we are expecting a reply to + * + * This only uses the most significant bits of the packet ID, both to save space + * and to avoid using the higher bits that can be modified during armor() to + * mask against the packet send counter used for QoS detection. + * + * @param packetId Packet ID to check + * @return True if we're expecting a reply + */ + inline bool expectingReplyTo(const uint64_t packetId) const + { + const uint32_t pid2 = (uint32_t)(packetId >> 32); + const unsigned long bucket = (unsigned long)(pid2 & ZT_EXPECTING_REPLIES_BUCKET_MASK1); + for(unsigned long i=0;i<=ZT_EXPECTING_REPLIES_BUCKET_MASK2;++i) { + if (_expectingRepliesTo[bucket][i] == pid2) + return true; + } + return false; + } + + /** + * Check whether we should do potentially expensive identity verification (rate limit) + * + * @param now Current time + * @param from Source address of packet + * @return True if within rate limits + */ + inline bool rateGateIdentityVerification(const uint64_t now,const InetAddress &from) + { + unsigned long iph = from.rateGateHash(); + if ((now - _lastIdentityVerification[iph]) >= ZT_IDENTITY_VALIDATION_SOURCE_RATE_LIMIT) { + _lastIdentityVerification[iph] = now; + return true; + } + return false; + } + + virtual void ncSendConfig(uint64_t nwid,uint64_t requestPacketId,const Address &destination,const NetworkConfig &nc,bool sendLegacyFormatConfig); + virtual void ncSendRevocation(const Address &destination,const Revocation &rev); + virtual void ncSendError(uint64_t nwid,uint64_t requestPacketId,const Address &destination,NetworkController::ErrorCode errorCode); + +private: + inline SharedPtr _network(uint64_t nwid) const + { + // assumes _networks_m is locked + for(std::vector< std::pair< uint64_t, SharedPtr > >::const_iterator i=_networks.begin();i!=_networks.end();++i) { + if (i->first == nwid) + return i->second; + } + return SharedPtr(); + } + + RuntimeEnvironment _RR; + RuntimeEnvironment *RR; + void *_uPtr; // _uptr (lower case) is reserved in Visual Studio :P + ZT_Node_Callbacks _cb; + + // For tracking packet IDs to filter out OK/ERROR replies to packets we did not send + uint8_t _expectingRepliesToBucketPtr[ZT_EXPECTING_REPLIES_BUCKET_MASK1 + 1]; + uint32_t _expectingRepliesTo[ZT_EXPECTING_REPLIES_BUCKET_MASK1 + 1][ZT_EXPECTING_REPLIES_BUCKET_MASK2 + 1]; + + // Time of last identity verification indexed by InetAddress.rateGateHash() -- used in IncomingPacket::_doHELLO() via rateGateIdentityVerification() + uint64_t _lastIdentityVerification[16384]; + + std::vector< std::pair< uint64_t, SharedPtr > > _networks; + Mutex _networks_m; + + std::vector< ZT_CircuitTest * > _circuitTests; + Mutex _circuitTests_m; + + std::vector _directPaths; + Mutex _directPaths_m; + + Mutex _backgroundTasksLock; + + uint64_t _now; + uint64_t _lastPingCheck; + uint64_t _lastHousekeepingRun; + volatile uint64_t _prngState[2]; + bool _online; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/NonCopyable.hpp b/node/NonCopyable.hpp new file mode 100644 index 0000000..6d4daa8 --- /dev/null +++ b/node/NonCopyable.hpp @@ -0,0 +1,38 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_NONCOPYABLE_HPP__ +#define ZT_NONCOPYABLE_HPP__ + +namespace ZeroTier { + +/** + * A simple concept that belongs in the C++ language spec + */ +class NonCopyable +{ +protected: + NonCopyable() throw() {} +private: + NonCopyable(const NonCopyable&); + const NonCopyable& operator=(const NonCopyable&); +}; + +} // namespace ZeroTier + +#endif diff --git a/node/OutboundMulticast.cpp b/node/OutboundMulticast.cpp new file mode 100644 index 0000000..285bfa5 --- /dev/null +++ b/node/OutboundMulticast.cpp @@ -0,0 +1,103 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include "Constants.hpp" +#include "RuntimeEnvironment.hpp" +#include "OutboundMulticast.hpp" +#include "Switch.hpp" +#include "Network.hpp" +#include "Node.hpp" +#include "Peer.hpp" +#include "Topology.hpp" + +namespace ZeroTier { + +void OutboundMulticast::init( + const RuntimeEnvironment *RR, + uint64_t timestamp, + uint64_t nwid, + bool disableCompression, + unsigned int limit, + unsigned int gatherLimit, + const MAC &src, + const MulticastGroup &dest, + unsigned int etherType, + const void *payload, + unsigned int len) +{ + uint8_t flags = 0; + + _timestamp = timestamp; + _nwid = nwid; + if (src) { + _macSrc = src; + flags |= 0x04; + } else { + _macSrc.fromAddress(RR->identity.address(),nwid); + } + _macDest = dest.mac(); + _limit = limit; + _frameLen = (len < ZT_MAX_MTU) ? len : ZT_MAX_MTU; + _etherType = etherType; + + if (gatherLimit) flags |= 0x02; + + /* + TRACE(">>MC %.16llx INIT %.16llx/%s limit %u gatherLimit %u from %s to %s length %u", + (unsigned long long)this, + nwid, + dest.toString().c_str(), + limit, + gatherLimit, + (src) ? src.toString().c_str() : MAC(RR->identity.address(),nwid).toString().c_str(), + dest.toString().c_str(), + len); + */ + + _packet.setSource(RR->identity.address()); + _packet.setVerb(Packet::VERB_MULTICAST_FRAME); + _packet.append((uint64_t)nwid); + _packet.append(flags); + if (gatherLimit) _packet.append((uint32_t)gatherLimit); + if (src) src.appendTo(_packet); + dest.mac().appendTo(_packet); + _packet.append((uint32_t)dest.adi()); + _packet.append((uint16_t)etherType); + _packet.append(payload,_frameLen); + if (!disableCompression) + _packet.compress(); + + memcpy(_frameData,payload,_frameLen); +} + +void OutboundMulticast::sendOnly(const RuntimeEnvironment *RR,void *tPtr,const Address &toAddr) +{ + const SharedPtr nw(RR->node->network(_nwid)); + const Address toAddr2(toAddr); + if ((nw)&&(nw->filterOutgoingPacket(tPtr,true,RR->identity.address(),toAddr2,_macSrc,_macDest,_frameData,_frameLen,_etherType,0))) { + //TRACE(">>MC %.16llx -> %s",(unsigned long long)this,toAddr.toString().c_str()); + _packet.newInitializationVector(); + _packet.setDestination(toAddr2); + RR->node->expectReplyTo(_packet.packetId()); + + Packet tmp(_packet); // make a copy of packet so as not to garble the original -- GitHub issue #461 + RR->sw->send(tPtr,tmp,true); + } +} + +} // namespace ZeroTier diff --git a/node/OutboundMulticast.hpp b/node/OutboundMulticast.hpp new file mode 100644 index 0000000..0ecf113 --- /dev/null +++ b/node/OutboundMulticast.hpp @@ -0,0 +1,153 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_OUTBOUNDMULTICAST_HPP +#define ZT_OUTBOUNDMULTICAST_HPP + +#include + +#include +#include + +#include "Constants.hpp" +#include "MAC.hpp" +#include "MulticastGroup.hpp" +#include "Address.hpp" +#include "Packet.hpp" + +namespace ZeroTier { + +class CertificateOfMembership; +class RuntimeEnvironment; + +/** + * An outbound multicast packet + * + * This object isn't guarded by a mutex; caller must synchronize access. + */ +class OutboundMulticast +{ +public: + /** + * Create an uninitialized outbound multicast + * + * It must be initialized with init(). + */ + OutboundMulticast() {} + + /** + * Initialize outbound multicast + * + * @param RR Runtime environment + * @param timestamp Creation time + * @param nwid Network ID + * @param disableCompression Disable compression of frame payload + * @param limit Multicast limit for desired number of packets to send + * @param gatherLimit Number to lazily/implicitly gather with this frame or 0 for none + * @param src Source MAC address of frame or NULL to imply compute from sender ZT address + * @param dest Destination multicast group (MAC + ADI) + * @param etherType 16-bit Ethernet type ID + * @param payload Data + * @param len Length of data + * @throws std::out_of_range Data too large to fit in a MULTICAST_FRAME + */ + void init( + const RuntimeEnvironment *RR, + uint64_t timestamp, + uint64_t nwid, + bool disableCompression, + unsigned int limit, + unsigned int gatherLimit, + const MAC &src, + const MulticastGroup &dest, + unsigned int etherType, + const void *payload, + unsigned int len); + + /** + * @return Multicast creation time + */ + inline uint64_t timestamp() const throw() { return _timestamp; } + + /** + * @param now Current time + * @return True if this multicast is expired (has exceeded transmit timeout) + */ + inline bool expired(uint64_t now) const throw() { return ((now - _timestamp) >= ZT_MULTICAST_TRANSMIT_TIMEOUT); } + + /** + * @return True if this outbound multicast has been sent to enough peers + */ + inline bool atLimit() const throw() { return (_alreadySentTo.size() >= _limit); } + + /** + * Just send without checking log + * + * @param RR Runtime environment + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param toAddr Destination address + */ + void sendOnly(const RuntimeEnvironment *RR,void *tPtr,const Address &toAddr); + + /** + * Just send and log but do not check sent log + * + * @param RR Runtime environment + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param toAddr Destination address + */ + inline void sendAndLog(const RuntimeEnvironment *RR,void *tPtr,const Address &toAddr) + { + _alreadySentTo.push_back(toAddr); + sendOnly(RR,tPtr,toAddr); + } + + /** + * Try to send this to a given peer if it hasn't been sent to them already + * + * @param RR Runtime environment + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param toAddr Destination address + * @return True if address is new and packet was sent to switch, false if duplicate + */ + inline bool sendIfNew(const RuntimeEnvironment *RR,void *tPtr,const Address &toAddr) + { + if (std::find(_alreadySentTo.begin(),_alreadySentTo.end(),toAddr) == _alreadySentTo.end()) { + sendAndLog(RR,tPtr,toAddr); + return true; + } else { + return false; + } + } + +private: + uint64_t _timestamp; + uint64_t _nwid; + MAC _macSrc; + MAC _macDest; + unsigned int _limit; + unsigned int _frameLen; + unsigned int _etherType; + Packet _packet; + std::vector
_alreadySentTo; + uint8_t _frameData[ZT_MAX_MTU]; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Packet.cpp b/node/Packet.cpp new file mode 100644 index 0000000..8a57dd5 --- /dev/null +++ b/node/Packet.cpp @@ -0,0 +1,1237 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include +#include + +#include "Packet.hpp" + +#ifdef ZT_USE_X64_ASM_SALSA2012 +#include "../ext/x64-salsa2012-asm/salsa2012.h" +#endif +#ifdef ZT_USE_ARM32_NEON_ASM_SALSA2012 +#include "../ext/arm32-neon-salsa2012-asm/salsa2012.h" +#endif + +#ifdef _MSC_VER +#define FORCE_INLINE static __forceinline +#include +#pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +#pragma warning(disable : 4293) /* disable: C4293: too large shift (32-bits) */ +#else +#define FORCE_INLINE static inline +#endif + +namespace ZeroTier { + +/************************************************************************** */ + +/* Set up macros for fast single-pass ASM Salsa20/12 crypto, if we have it */ + +// x64 SSE crypto +#ifdef ZT_USE_X64_ASM_SALSA2012 +#define ZT_HAS_FAST_CRYPTO() (true) +#define ZT_FAST_SINGLE_PASS_SALSA2012(b,l,n,k) zt_salsa2012_amd64_xmm6(reinterpret_cast(b),(l),reinterpret_cast(n),reinterpret_cast(k)) +#endif + +// ARM (32-bit) NEON crypto (must be detected) +#ifdef ZT_USE_ARM32_NEON_ASM_SALSA2012 +class _FastCryptoChecker +{ +public: + _FastCryptoChecker() : canHas(zt_arm_has_neon()) {} + bool canHas; +}; +static const _FastCryptoChecker _ZT_FAST_CRYPTO_CHECK; +#define ZT_HAS_FAST_CRYPTO() (_ZT_FAST_CRYPTO_CHECK.canHas) +#define ZT_FAST_SINGLE_PASS_SALSA2012(b,l,n,k) zt_salsa2012_armneon3_xor(reinterpret_cast(b),(const unsigned char *)0,(l),reinterpret_cast(n),reinterpret_cast(k)) +#endif + +// No fast crypto available +#ifndef ZT_HAS_FAST_CRYPTO +#define ZT_HAS_FAST_CRYPTO() (false) +#define ZT_FAST_SINGLE_PASS_SALSA2012(b,l,n,k) {} +#endif + +/************************************************************************** */ + +/* LZ4 is shipped encapsulated into Packet in an anonymous namespace. + * + * We're doing this as a deliberate workaround for various Linux distribution + * policies that forbid static linking of support libraries. + * + * The reason is that relying on distribution versions of LZ4 has been too + * big a source of bugs and compatibility issues. The LZ4 API is not stable + * enough across versions, and dependency hell ensues. So fark it. */ + +/* Needless to say the code in this anonymous namespace should be considered + * BSD 2-clause licensed. */ + +namespace { + +/* lz4.h ------------------------------------------------------------------ */ + +/* + * LZ4 - Fast LZ compression algorithm + * Header File + * Copyright (C) 2011-2016, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ + +/** + Introduction + + LZ4 is lossless compression algorithm, providing compression speed at 400 MB/s per core, + scalable with multi-cores CPU. It features an extremely fast decoder, with speed in + multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. + + The LZ4 compression library provides in-memory compression and decompression functions. + Compression can be done in: + - a single step (described as Simple Functions) + - a single step, reusing a context (described in Advanced Functions) + - unbounded multiple steps (described as Streaming compression) + + lz4.h provides block compression functions. It gives full buffer control to user. + Decompressing an lz4-compressed block also requires metadata (such as compressed size). + Each application is free to encode such metadata in whichever way it wants. + + An additional format, called LZ4 frame specification (doc/lz4_Frame_format.md), + take care of encoding standard metadata alongside LZ4-compressed blocks. + If your application requires interoperability, it's recommended to use it. + A library is provided to take care of it, see lz4frame.h. +*/ + +#define LZ4LIB_API + +/*========== Version =========== */ +#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ +#define LZ4_VERSION_MINOR 7 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 5 /* for tweaks, bug-fixes, or development */ + +#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) + +#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE +#define LZ4_QUOTE(str) #str +#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) + +/*! + * LZ4_MEMORY_USAGE : + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.) + * Increasing memory usage improves compression ratio + * Reduced memory usage can improve speed, due to cache effect + * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache + */ +#define LZ4_MEMORY_USAGE 14 + +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + +/*-********************************************* +* Streaming Compression Functions +***********************************************/ +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ + +/*! LZ4_resetStream() : + * An LZ4_stream_t structure can be allocated once and re-used multiple times. + * Use this function to init an allocated `LZ4_stream_t` structure and start a new compression. + */ +LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); + +/*^********************************************** + * !!!!!! STATIC LINKING ONLY !!!!!! + ***********************************************/ +/*-************************************ + * Private definitions + ************************************** + * Do not use these definitions. + * They are exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. + * Using these definitions will expose code to API and/or ABI break in future versions of the library. + **************************************/ +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +typedef struct { + uint32_t hashTable[LZ4_HASH_SIZE_U32]; + uint32_t currentOffset; + uint32_t initCheck; + const uint8_t* dictionary; + uint8_t* bufferStart; /* obsolete, used for slideInputBuffer */ + uint32_t dictSize; +} LZ4_stream_t_internal; + +typedef struct { + const uint8_t* externalDict; + size_t extDictSize; + const uint8_t* prefixEnd; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +/*! + * LZ4_stream_t : + * information structure to track an LZ4 stream. + * init this structure before first use. + * note : only use in association with static linking ! + * this definition is not API/ABI safe, + * and may change in a future version ! + */ +#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4) +#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long)) +union LZ4_stream_u { + unsigned long long table[LZ4_STREAMSIZE_U64]; + LZ4_stream_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_stream_t */ + +/*! + * LZ4_streamDecode_t : + * information structure to track an LZ4 stream during decompression. + * init this structure using LZ4_setStreamDecode (or memset()) before first use + * note : only use in association with static linking ! + * this definition is not API/ABI safe, + * and may change in a future version ! + */ +#define LZ4_STREAMDECODESIZE_U64 4 +#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long)) +union LZ4_streamDecode_u { + unsigned long long table[LZ4_STREAMDECODESIZE_U64]; + LZ4_streamDecode_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_streamDecode_t */ + +#ifndef HEAPMODE +# define HEAPMODE 0 +#endif + +//#define ACCELERATION_DEFAULT 1 + +/* LZ4_FORCE_MEMORY_ACCESS + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method is portable but violate C standard. + * It can generate buggy code on targets which generate assembly depending on alignment. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#if 0 +#ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ +# if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define LZ4_FORCE_MEMORY_ACCESS 2 +# elif defined(__INTEL_COMPILER) || \ + (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) )) +# define LZ4_FORCE_MEMORY_ACCESS 1 +# endif +#endif +#endif + +#ifdef ZT_NO_TYPE_PUNNING +#define LZ4_FORCE_MEMORY_ACCESS 0 +#else +#define LZ4_FORCE_MEMORY_ACCESS 2 +#endif + +/* + * LZ4_FORCE_SW_BITCOUNT + * Define this parameter if your target system or compiler does not support hardware bit count + */ +#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for Windows CE does not support Hardware bit count */ +# define LZ4_FORCE_SW_BITCOUNT +#endif + +/*-************************************ +* Compiler Options +**************************************/ +#if 0 +#ifdef _MSC_VER /* Visual Studio */ +# define FORCE_INLINE static __forceinline +# include +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# pragma warning(disable : 4293) /* disable: C4293: too large shift (32-bits) */ +#else +# if defined(__GNUC__) || defined(__clang__) +# define FORCE_INLINE static inline __attribute__((always_inline)) +# elif defined(__cplusplus) || (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# define FORCE_INLINE static inline +# else +# define FORCE_INLINE static +# endif +#endif /* _MSC_VER */ +#endif + +#ifndef FORCE_INLINE +#define FORCE_INLINE static inline +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#define likely(expr) expect((expr) != 0, 1) +#define unlikely(expr) expect((expr) != 0, 0) + +/*-************************************ +* Memory routines +**************************************/ +//#include /* malloc, calloc, free */ +#define ALLOCATOR(n,s) calloc(n,s) +#define FREEMEM free +//#include /* memset, memcpy */ +#define MEM_INIT memset + +/*-************************************ +* Basic Types +**************************************/ +typedef uint8_t BYTE; +typedef uint16_t U16; +typedef uint32_t U32; +typedef int32_t S32; +typedef uint64_t U64; +typedef uintptr_t uptrval; +typedef uintptr_t reg_t; + +/*-************************************ +* Reading and writing into memory +**************************************/ +static unsigned LZ4_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} + +#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2) +/* lie to the compiler about data alignment; use with caution */ + +static U16 LZ4_read16(const void* memPtr) { return *(const U16*) memPtr; } +static U32 LZ4_read32(const void* memPtr) { return *(const U32*) memPtr; } +static reg_t LZ4_read_ARCH(const void* memPtr) { return *(const reg_t*) memPtr; } + +static void LZ4_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; } +static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; } + +#elif defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==1) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U16 u16; U32 u32; reg_t uArch; } __attribute__((packed)) unalign; + +static U16 LZ4_read16(const void* ptr) { return ((const unalign*)ptr)->u16; } +static U32 LZ4_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } +static reg_t LZ4_read_ARCH(const void* ptr) { return ((const unalign*)ptr)->uArch; } + +static void LZ4_write16(void* memPtr, U16 value) { ((unalign*)memPtr)->u16 = value; } +static void LZ4_write32(void* memPtr, U32 value) { ((unalign*)memPtr)->u32 = value; } + +#else /* safe and portable access through memcpy() */ + +static inline U16 LZ4_read16(const void* memPtr) +{ + U16 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static inline U32 LZ4_read32(const void* memPtr) +{ + U32 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static inline reg_t LZ4_read_ARCH(const void* memPtr) +{ + reg_t val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static inline void LZ4_write16(void* memPtr, U16 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +static inline void LZ4_write32(void* memPtr, U32 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +#endif /* LZ4_FORCE_MEMORY_ACCESS */ + +static inline U16 LZ4_readLE16(const void* memPtr) +{ + if (LZ4_isLittleEndian()) { + return LZ4_read16(memPtr); + } else { + const BYTE* p = (const BYTE*)memPtr; + return (U16)((U16)p[0] + (p[1]<<8)); + } +} + +static inline void LZ4_writeLE16(void* memPtr, U16 value) +{ + if (LZ4_isLittleEndian()) { + LZ4_write16(memPtr, value); + } else { + BYTE* p = (BYTE*)memPtr; + p[0] = (BYTE) value; + p[1] = (BYTE)(value>>8); + } +} + +static inline void LZ4_copy8(void* dst, const void* src) +{ + memcpy(dst,src,8); +} + +/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ +static inline void LZ4_wildCopy(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { LZ4_copy8(d,s); d+=8; s+=8; } while (d>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_ctzll((U64)val) >> 3); +# else + static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2, 0, 3, 1, 3, 1, 4, 2, 7, 0, 2, 3, 6, 1, 5, 3, 5, 1, 3, 4, 4, 2, 5, 6, 7, 7, 0, 1, 2, 3, 3, 4, 6, 2, 6, 5, 5, 3, 4, 5, 6, 7, 1, 2, 4, 6, 4, 4, 5, 7, 2, 6, 5, 7, 6, 7, 7 }; + return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58]; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r; + _BitScanForward( &r, (U32)val ); + return (int)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_ctz((U32)val) >> 3); +# else + static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0, 3, 2, 2, 1, 3, 2, 0, 1, 3, 3, 1, 2, 2, 2, 2, 0, 3, 1, 2, 0, 1, 0, 1, 1 }; + return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27]; +# endif + } + } else /* Big Endian CPU */ { + if (sizeof(val)==8) { +# if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse64( &r, val ); + return (unsigned)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_clzll((U64)val) >> 3); +# else + unsigned r; + if (!(val>>32)) { r=4; } else { r=0; val>>=32; } + if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } + r += (!val); + return r; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse( &r, (unsigned long)val ); + return (unsigned)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_clz((U32)val) >> 3); +# else + unsigned r; + if (!(val>>16)) { r=2; val>>=8; } else { r=0; val>>=24; } + r += (!val); + return r; +# endif + } + } +} + +#define STEPSIZE sizeof(reg_t) +static inline unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit) +{ + const BYTE* const pStart = pIn; + + while (likely(pIn compression run slower on incompressible data */ + +/*-************************************ +* Local Structures and types +**************************************/ +typedef enum { notLimited = 0, limitedOutput = 1 } limitedOutput_directive; +typedef enum { byPtr, byU32, byU16 } tableType_t; + +typedef enum { noDict = 0, withPrefix64k, usingExtDict } dict_directive; +typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; + +typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; +typedef enum { full = 0, partial = 1 } earlyEnd_directive; + +/*-************************************ +* Local Utils +**************************************/ +//int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; } +//const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; } +int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } +//int LZ4_sizeofState() { return LZ4_STREAMSIZE; } + +/*-****************************** +* Compression functions +********************************/ +static inline U32 LZ4_hash4(U32 sequence, tableType_t const tableType) +{ + if (tableType == byU16) + return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1))); + else + return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); +} + +static inline U32 LZ4_hash5(U64 sequence, tableType_t const tableType) +{ + static const U64 prime5bytes = 889523592379ULL; + static const U64 prime8bytes = 11400714785074694791ULL; + const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; + if (LZ4_isLittleEndian()) + return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); + else + return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); +} + +FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) +{ + if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + return LZ4_hash4(LZ4_read32(p), tableType); +} + +static inline void LZ4_putPositionOnHash(const BYTE* p, U32 h, void* tableBase, tableType_t const tableType, const BYTE* srcBase) +{ + switch (tableType) + { + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; } + } +} + +FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase); +} + +static inline const BYTE* LZ4_getPositionOnHash(U32 h, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + if (tableType == byPtr) { const BYTE** hashTable = (const BYTE**) tableBase; return hashTable[h]; } + if (tableType == byU32) { const U32* const hashTable = (U32*) tableBase; return hashTable[h] + srcBase; } + { const U16* const hashTable = (U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ +} + +FORCE_INLINE const BYTE* LZ4_getPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase); +} + +/** LZ4_compress_generic() : + inlined, to ensure branches are decided at compilation time */ +FORCE_INLINE int LZ4_compress_generic( + LZ4_stream_t_internal* const cctx, + const char* const source, + char* const dest, + const int inputSize, + const int maxOutputSize, + const limitedOutput_directive outputLimited, + const tableType_t tableType, + const dict_directive dict, + const dictIssue_directive dictIssue, + const U32 acceleration) +{ + const BYTE* ip = (const BYTE*) source; + const BYTE* base; + const BYTE* lowLimit; + const BYTE* const lowRefLimit = ip - cctx->dictSize; + const BYTE* const dictionary = cctx->dictionary; + const BYTE* const dictEnd = dictionary + cctx->dictSize; + const ptrdiff_t dictDelta = dictEnd - (const BYTE*)source; + const BYTE* anchor = (const BYTE*) source; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = iend - LASTLITERALS; + + BYTE* op = (BYTE*) dest; + BYTE* const olimit = op + maxOutputSize; + + U32 forwardH; + + /* Init conditions */ + if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported inputSize, too large (or negative) */ + switch(dict) + { + case noDict: + default: + base = (const BYTE*)source; + lowLimit = (const BYTE*)source; + break; + case withPrefix64k: + base = (const BYTE*)source - cctx->currentOffset; + lowLimit = (const BYTE*)source - cctx->dictSize; + break; + case usingExtDict: + base = (const BYTE*)source - cctx->currentOffset; + lowLimit = (const BYTE*)source; + break; + } + if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ + if (inputSizehashTable, tableType, base); + ip++; forwardH = LZ4_hashPosition(ip, tableType); + + /* Main Loop */ + for ( ; ; ) { + ptrdiff_t refDelta = 0; + const BYTE* match; + BYTE* token; + + /* Find a match */ + { const BYTE* forwardIp = ip; + unsigned step = 1; + unsigned searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimit)) goto _last_literals; + + match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); + if (dict==usingExtDict) { + if (match < (const BYTE*)source) { + refDelta = dictDelta; + lowLimit = dictionary; + } else { + refDelta = 0; + lowLimit = (const BYTE*)source; + } } + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); + + } while ( ((dictIssue==dictSmall) ? (match < lowRefLimit) : 0) + || ((tableType==byU16) ? 0 : (match + MAX_DISTANCE < ip)) + || (LZ4_read32(match+refDelta) != LZ4_read32(ip)) ); + } + + /* Catch up */ + while (((ip>anchor) & (match+refDelta > lowLimit)) && (unlikely(ip[-1]==match[refDelta-1]))) { ip--; match--; } + + /* Encode Literals */ + { unsigned const litLength = (unsigned)(ip - anchor); + token = op++; + if ((outputLimited) && /* Check output buffer overflow */ + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit))) + return 0; + if (litLength >= RUN_MASK) { + int len = (int)litLength-RUN_MASK; + *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; + *op++ = (BYTE)len; + } + else *token = (BYTE)(litLength< matchlimit) limit = matchlimit; + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); + ip += MINMATCH + matchCode; + if (ip==limit) { + unsigned const more = LZ4_count(ip, (const BYTE*)source, matchlimit); + matchCode += more; + ip += more; + } + } else { + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); + ip += MINMATCH + matchCode; + } + + if ( outputLimited && /* Check output buffer overflow */ + (unlikely(op + (1 + LASTLITERALS) + (matchCode>>8) > olimit)) ) + return 0; + if (matchCode >= ML_MASK) { + *token += ML_MASK; + matchCode -= ML_MASK; + LZ4_write32(op, 0xFFFFFFFF); + while (matchCode >= 4*255) op+=4, LZ4_write32(op, 0xFFFFFFFF), matchCode -= 4*255; + op += matchCode / 255; + *op++ = (BYTE)(matchCode % 255); + } else + *token += (BYTE)(matchCode); + } + + anchor = ip; + + /* Test end of chunk */ + if (ip > mflimit) break; + + /* Fill table */ + LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); + + /* Test next position */ + match = LZ4_getPosition(ip, cctx->hashTable, tableType, base); + if (dict==usingExtDict) { + if (match < (const BYTE*)source) { + refDelta = dictDelta; + lowLimit = dictionary; + } else { + refDelta = 0; + lowLimit = (const BYTE*)source; + } } + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + if ( ((dictIssue==dictSmall) ? (match>=lowRefLimit) : 1) + && (match+MAX_DISTANCE>=ip) + && (LZ4_read32(match+refDelta)==LZ4_read32(ip)) ) + { token=op++; *token=0; goto _next_match; } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ip, tableType); + } + +_last_literals: + /* Encode Last Literals */ + { size_t const lastRun = (size_t)(iend - anchor); + if ( (outputLimited) && /* Check output buffer overflow */ + ((op - (BYTE*)dest) + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > (U32)maxOutputSize) ) + return 0; + if (lastRun >= RUN_MASK) { + size_t accumulator = lastRun - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRun<internal_donotuse; + LZ4_resetStream((LZ4_stream_t*)state); + //if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + + if (maxOutputSize >= LZ4_compressBound(inputSize)) { + if (inputSize < LZ4_64Klimit) + return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, byU16, noDict, noDictIssue, acceleration); + else + return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, (sizeof(void*)==8) ? byU32 : byPtr, noDict, noDictIssue, acceleration); + } else { + if (inputSize < LZ4_64Klimit) + return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + else + return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, (sizeof(void*)==8) ? byU32 : byPtr, noDict, noDictIssue, acceleration); + } +} + +static inline int LZ4_compress_fast(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ +#if (HEAPMODE) + void* ctxPtr = ALLOCATOR(1, sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ +#else + LZ4_stream_t ctx; + void* const ctxPtr = &ctx; +#endif + + int const result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); + +#if (HEAPMODE) + FREEMEM(ctxPtr); +#endif + return result; +} + +void LZ4_resetStream (LZ4_stream_t* LZ4_stream) +{ + MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t)); +} + +/*-***************************** +* Decompression functions +*******************************/ +/*! LZ4_decompress_generic() : + * This generic decompression function cover all use cases. + * It shall be instantiated several times, using different sets of directives + * Note that it is important this generic function is really inlined, + * in order to remove useless branches during compilation optimization. + */ +FORCE_INLINE int LZ4_decompress_generic( + const char* const source, + char* const dest, + int inputSize, + int outputSize, /* If endOnInput==endOnInputSize, this value is the max size of Output Buffer. */ + + int endOnInput, /* endOnOutputSize, endOnInputSize */ + int partialDecoding, /* full, partial */ + int targetOutputSize, /* only used if partialDecoding==partial */ + int dict, /* noDict, withPrefix64k, usingExtDict */ + const BYTE* const lowPrefix, /* == dest when no prefix */ + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note : = 0 if noDict */ + ) +{ + /* Local Variables */ + const BYTE* ip = (const BYTE*) source; + const BYTE* const iend = ip + inputSize; + + BYTE* op = (BYTE*) dest; + BYTE* const oend = op + outputSize; + BYTE* cpy; + BYTE* oexit = op + targetOutputSize; + const BYTE* const lowLimit = lowPrefix - dictSize; + + const BYTE* const dictEnd = (const BYTE*)dictStart + dictSize; + const unsigned dec32table[] = {0, 1, 2, 1, 4, 4, 4, 4}; + const int dec64table[] = {0, 0, 0, -1, 0, 1, 2, 3}; + + const int safeDecode = (endOnInput==endOnInputSize); + const int checkOffset = ((safeDecode) && (dictSize < (int)(64 KB))); + + + /* Special cases */ + if ((partialDecoding) && (oexit > oend-MFLIMIT)) oexit = oend-MFLIMIT; /* targetOutputSize too high => decode everything */ + if ((endOnInput) && (unlikely(outputSize==0))) return ((inputSize==1) && (*ip==0)) ? 0 : -1; /* Empty output buffer */ + if ((!endOnInput) && (unlikely(outputSize==0))) return (*ip==0?1:-1); + + /* Main Loop : decode sequences */ + while (1) { + size_t length; + const BYTE* match; + size_t offset; + + /* get literal length */ + unsigned const token = *ip++; + if ((length=(token>>ML_BITS)) == RUN_MASK) { + unsigned s; + do { + s = *ip++; + length += s; + } while ( likely(endOnInput ? ip(partialDecoding?oexit:oend-MFLIMIT)) || (ip+length>iend-(2+1+LASTLITERALS))) ) + || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) + { + if (partialDecoding) { + if (cpy > oend) goto _output_error; /* Error : write attempt beyond end of output buffer */ + if ((endOnInput) && (ip+length > iend)) goto _output_error; /* Error : read attempt beyond end of input buffer */ + } else { + if ((!endOnInput) && (cpy != oend)) goto _output_error; /* Error : block decoding must stop exactly there */ + if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) goto _output_error; /* Error : input must be consumed */ + } + memcpy(op, ip, length); + ip += length; + op += length; + break; /* Necessarily EOF, due to parsing restrictions */ + } + LZ4_wildCopy(op, ip, cpy); + ip += length; op = cpy; + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + if ((checkOffset) && (unlikely(match < lowLimit))) goto _output_error; /* Error : offset outside buffers */ + LZ4_write32(op, (U32)offset); /* costs ~1%; silence an msan warning when offset==0 */ + + /* get matchlength */ + length = token & ML_MASK; + if (length == ML_MASK) { + unsigned s; + do { + s = *ip++; + if ((endOnInput) && (ip > iend-LASTLITERALS)) goto _output_error; + length += s; + } while (s==255); + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + } + length += MINMATCH; + + /* check external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + if (unlikely(op+length > oend-LASTLITERALS)) goto _output_error; /* doesn't respect parsing restriction */ + + if (length <= (size_t)(lowPrefix-match)) { + /* match can be copied as a single segment from external dictionary */ + memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match encompass external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix-match); + size_t const restSize = length - copySize; + memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op-lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) *op++ = *copyFrom++; + } else { + memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + + /* copy match within block */ + cpy = op + length; + if (unlikely(offset<8)) { + const int dec64 = dec64table[offset]; + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += dec32table[offset]; + memcpy(op+4, match, 4); + match -= dec64; + } else { LZ4_copy8(op, match); match+=8; } + op += 8; + + if (unlikely(cpy>oend-12)) { + BYTE* const oCopyLimit = oend-(WILDCOPYLENGTH-1); + if (cpy > oend-LASTLITERALS) goto _output_error; /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (op < oCopyLimit) { + LZ4_wildCopy(op, match, oCopyLimit); + match += oCopyLimit - op; + op = oCopyLimit; + } + while (op16) LZ4_wildCopy(op+8, match+8, cpy); + } + op=cpy; /* correction */ + } + + /* end of decoding */ + if (endOnInput) + return (int) (((char*)op)-dest); /* Nb of output bytes decoded */ + else + return (int) (((const char*)ip)-source); /* Nb of input bytes read */ + + /* Overflow error detected */ +_output_error: + return (int) (-(((const char*)ip)-source))-1; +} + +static inline int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, endOnInputSize, full, 0, noDict, (BYTE*)dest, NULL, 0); +} + +} // anonymous namespace + +/************************************************************************** */ +/************************************************************************** */ + +const unsigned char Packet::ZERO_KEY[32] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; + +#ifdef ZT_TRACE + +const char *Packet::verbString(Verb v) +{ + switch(v) { + case VERB_NOP: return "NOP"; + case VERB_HELLO: return "HELLO"; + case VERB_ERROR: return "ERROR"; + case VERB_OK: return "OK"; + case VERB_WHOIS: return "WHOIS"; + case VERB_RENDEZVOUS: return "RENDEZVOUS"; + case VERB_FRAME: return "FRAME"; + case VERB_EXT_FRAME: return "EXT_FRAME"; + case VERB_ECHO: return "ECHO"; + case VERB_MULTICAST_LIKE: return "MULTICAST_LIKE"; + case VERB_NETWORK_CREDENTIALS: return "NETWORK_CREDENTIALS"; + case VERB_NETWORK_CONFIG_REQUEST: return "NETWORK_CONFIG_REQUEST"; + case VERB_NETWORK_CONFIG: return "NETWORK_CONFIG"; + case VERB_MULTICAST_GATHER: return "MULTICAST_GATHER"; + case VERB_MULTICAST_FRAME: return "MULTICAST_FRAME"; + case VERB_PUSH_DIRECT_PATHS: return "PUSH_DIRECT_PATHS"; + case VERB_CIRCUIT_TEST: return "CIRCUIT_TEST"; + case VERB_CIRCUIT_TEST_REPORT: return "CIRCUIT_TEST_REPORT"; + case VERB_USER_MESSAGE: return "USER_MESSAGE"; + } + return "(unknown)"; +} + +const char *Packet::errorString(ErrorCode e) +{ + switch(e) { + case ERROR_NONE: return "NONE"; + case ERROR_INVALID_REQUEST: return "INVALID_REQUEST"; + case ERROR_BAD_PROTOCOL_VERSION: return "BAD_PROTOCOL_VERSION"; + case ERROR_OBJ_NOT_FOUND: return "OBJECT_NOT_FOUND"; + case ERROR_IDENTITY_COLLISION: return "IDENTITY_COLLISION"; + case ERROR_UNSUPPORTED_OPERATION: return "UNSUPPORTED_OPERATION"; + case ERROR_NEED_MEMBERSHIP_CERTIFICATE: return "NEED_MEMBERSHIP_CERTIFICATE"; + case ERROR_NETWORK_ACCESS_DENIED_: return "NETWORK_ACCESS_DENIED"; + case ERROR_UNWANTED_MULTICAST: return "UNWANTED_MULTICAST"; + } + return "(unknown)"; +} + +#endif // ZT_TRACE + +void Packet::armor(const void *key,bool encryptPayload,unsigned int counter) +{ + uint8_t mangledKey[32]; + uint8_t *const data = reinterpret_cast(unsafeData()); + + // Mask least significant 3 bits of packet ID with counter to embed packet send counter for QoS use + data[7] = (data[7] & 0xf8) | (uint8_t)(counter & 0x07); + + // Set flag now, since it affects key mangle function + setCipher(encryptPayload ? ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012 : ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE); + + _salsa20MangleKey((const unsigned char *)key,mangledKey); + if (ZT_HAS_FAST_CRYPTO()) { + const unsigned int encryptLen = (encryptPayload) ? (size() - ZT_PACKET_IDX_VERB) : 0; + uint64_t keyStream[(ZT_PROTO_MAX_PACKET_LENGTH + 64 + 8) / 8]; + ZT_FAST_SINGLE_PASS_SALSA2012(keyStream,encryptLen + 64,(data + ZT_PACKET_IDX_IV),mangledKey); + Salsa20::memxor(data + ZT_PACKET_IDX_VERB,reinterpret_cast(keyStream + 8),encryptLen); + uint64_t mac[2]; + Poly1305::compute(mac,data + ZT_PACKET_IDX_VERB,size() - ZT_PACKET_IDX_VERB,keyStream); +#ifdef ZT_NO_TYPE_PUNNING + memcpy(data + ZT_PACKET_IDX_MAC,mac,8); +#else + (*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) = mac[0]; +#endif + } else { + Salsa20 s20(mangledKey,data + ZT_PACKET_IDX_IV); + uint64_t macKey[4]; + s20.crypt12(ZERO_KEY,macKey,sizeof(macKey)); + uint8_t *const payload = data + ZT_PACKET_IDX_VERB; + const unsigned int payloadLen = size() - ZT_PACKET_IDX_VERB; + if (encryptPayload) + s20.crypt12(payload,payload,payloadLen); + uint64_t mac[2]; + Poly1305::compute(mac,payload,payloadLen,macKey); + memcpy(data + ZT_PACKET_IDX_MAC,mac,8); + } +} + +bool Packet::dearmor(const void *key) +{ + uint8_t mangledKey[32]; + uint8_t *const data = reinterpret_cast(unsafeData()); + const unsigned int payloadLen = size() - ZT_PACKET_IDX_VERB; + unsigned char *const payload = data + ZT_PACKET_IDX_VERB; + const unsigned int cs = cipher(); + + if ((cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE)||(cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012)) { + _salsa20MangleKey((const unsigned char *)key,mangledKey); + if (ZT_HAS_FAST_CRYPTO()) { + uint64_t keyStream[(ZT_PROTO_MAX_PACKET_LENGTH + 64 + 8) / 8]; + ZT_FAST_SINGLE_PASS_SALSA2012(keyStream,((cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) ? (payloadLen + 64) : 64),(data + ZT_PACKET_IDX_IV),mangledKey); + uint64_t mac[2]; + Poly1305::compute(mac,payload,payloadLen,keyStream); +#ifdef ZT_NO_TYPE_PUNNING + if (!Utils::secureEq(mac,data + ZT_PACKET_IDX_MAC,8)) + return false; +#else + if ((*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) != mac[0]) // also secure, constant time + return false; +#endif + if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) + Salsa20::memxor(data + ZT_PACKET_IDX_VERB,reinterpret_cast(keyStream + 8),payloadLen); + } else { + Salsa20 s20(mangledKey,data + ZT_PACKET_IDX_IV); + uint64_t macKey[4]; + s20.crypt12(ZERO_KEY,macKey,sizeof(macKey)); + uint64_t mac[2]; + Poly1305::compute(mac,payload,payloadLen,macKey); +#ifdef ZT_NO_TYPE_PUNNING + if (!Utils::secureEq(mac,data + ZT_PACKET_IDX_MAC,8)) + return false; +#else + if ((*reinterpret_cast(data + ZT_PACKET_IDX_MAC)) != mac[0]) // also secure, constant time + return false; +#endif + if (cs == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) + s20.crypt12(payload,payload,payloadLen); + } + + return true; + } else { + return false; // unrecognized cipher suite + } +} + +void Packet::cryptField(const void *key,unsigned int start,unsigned int len) +{ + uint8_t *const data = reinterpret_cast(unsafeData()); + uint8_t iv[8]; + for(int i=0;i<8;++i) iv[i] = data[i]; + iv[7] &= 0xf8; // mask off least significant 3 bits of packet ID / IV since this is unset when this function gets called + Salsa20 s20(key,iv); + s20.crypt12(data + start,data + start,len); +} + +bool Packet::compress() +{ + char *const data = reinterpret_cast(unsafeData()); + char buf[ZT_PROTO_MAX_PACKET_LENGTH * 2]; + + if ((!compressed())&&(size() > (ZT_PACKET_IDX_PAYLOAD + 64))) { // don't bother compressing tiny packets + int pl = (int)(size() - ZT_PACKET_IDX_PAYLOAD); + int cl = LZ4_compress_fast(data + ZT_PACKET_IDX_PAYLOAD,buf,pl,ZT_PROTO_MAX_PACKET_LENGTH * 2,2); + if ((cl > 0)&&(cl < pl)) { + data[ZT_PACKET_IDX_VERB] |= (char)ZT_PROTO_VERB_FLAG_COMPRESSED; + setSize((unsigned int)cl + ZT_PACKET_IDX_PAYLOAD); + memcpy(data + ZT_PACKET_IDX_PAYLOAD,buf,cl); + return true; + } + } + data[ZT_PACKET_IDX_VERB] &= (char)(~ZT_PROTO_VERB_FLAG_COMPRESSED); + + return false; +} + +bool Packet::uncompress() +{ + char *const data = reinterpret_cast(unsafeData()); + char buf[ZT_PROTO_MAX_PACKET_LENGTH]; + + if ((compressed())&&(size() >= ZT_PROTO_MIN_PACKET_LENGTH)) { + if (size() > ZT_PACKET_IDX_PAYLOAD) { + unsigned int compLen = size() - ZT_PACKET_IDX_PAYLOAD; + int ucl = LZ4_decompress_safe((const char *)data + ZT_PACKET_IDX_PAYLOAD,buf,compLen,sizeof(buf)); + if ((ucl > 0)&&(ucl <= (int)(capacity() - ZT_PACKET_IDX_PAYLOAD))) { + setSize((unsigned int)ucl + ZT_PACKET_IDX_PAYLOAD); + memcpy(data + ZT_PACKET_IDX_PAYLOAD,buf,ucl); + } else { + return false; + } + } + data[ZT_PACKET_IDX_VERB] &= (char)(~ZT_PROTO_VERB_FLAG_COMPRESSED); + } + + return true; +} + +} // namespace ZeroTier diff --git a/node/Packet.hpp b/node/Packet.hpp new file mode 100644 index 0000000..8ad2c0f --- /dev/null +++ b/node/Packet.hpp @@ -0,0 +1,1457 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_N_PACKET_HPP +#define ZT_N_PACKET_HPP + +#include +#include +#include + +#include +#include + +#include "Constants.hpp" + +#include "Address.hpp" +#include "Poly1305.hpp" +#include "Salsa20.hpp" +#include "Utils.hpp" +#include "Buffer.hpp" + +//#ifdef ZT_USE_SYSTEM_LZ4 +//#include +//#else +//#include "../ext/lz4/lz4.h" +//#endif + +/** + * Protocol version -- incremented only for major changes + * + * 1 - 0.2.0 ... 0.2.5 + * 2 - 0.3.0 ... 0.4.5 + * + Added signature and originating peer to multicast frame + * + Double size of multicast frame bloom filter + * 3 - 0.5.0 ... 0.6.0 + * + Yet another multicast redesign + * + New crypto completely changes key agreement cipher + * 4 - 0.6.0 ... 1.0.6 + * + BREAKING CHANGE: New identity format based on hashcash design + * 5 - 1.1.0 ... 1.1.5 + * + Supports circuit test, proof of work, and echo + * + Supports in-band world (root server definition) updates + * + Clustering! (Though this will work with protocol v4 clients.) + * + Otherwise backward compatible with protocol v4 + * 6 - 1.1.5 ... 1.1.10 + * + Network configuration format revisions including binary values + * 7 - 1.1.10 ... 1.1.17 + * + Introduce trusted paths for local SDN use + * 8 - 1.1.17 ... 1.2.0 + * + Multipart network configurations for large network configs + * + Tags and Capabilities + * + Inline push of CertificateOfMembership deprecated + * + Certificates of representation for federation and mesh + * 9 - 1.2.0 ... CURRENT + * + In-band encoding of packet counter for link quality measurement + */ +#define ZT_PROTO_VERSION 9 + +/** + * Minimum supported protocol version + */ +#define ZT_PROTO_VERSION_MIN 4 + +/** + * Maximum hop count allowed by packet structure (3 bits, 0-7) + * + * This is a protocol constant. It's the maximum allowed by the length + * of the hop counter -- three bits. See node/Constants.hpp for the + * pragmatic forwarding limit, which is typically lower. + */ +#define ZT_PROTO_MAX_HOPS 7 + +/** + * Cipher suite: Curve25519/Poly1305/Salsa20/12/NOCRYPT + * + * This specifies Poly1305 MAC using a 32-bit key derived from the first + * 32 bytes of a Salsa20/12 keystream as in the Salsa20/12 cipher suite, + * but the payload is not encrypted. This is currently only used to send + * HELLO since that's the public key specification packet and must be + * sent in the clear. Key agreement is performed using Curve25519 elliptic + * curve Diffie-Hellman. + */ +#define ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_NONE 0 + +/** + * Cipher suite: Curve25519/Poly1305/Salsa20/12 + * + * This specifies Poly1305 using the first 32 bytes of a Salsa20/12 key + * stream as its one-time-use key followed by payload encryption with + * the remaining Salsa20/12 key stream. Key agreement is performed using + * Curve25519 elliptic curve Diffie-Hellman. + */ +#define ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012 1 + +/** + * Cipher suite: NONE + * + * This differs from POLY1305/NONE in that *no* crypto is done, not even + * authentication. This is for trusted local LAN interconnects for internal + * SDN use within a data center. + * + * For this mode the MAC field becomes a trusted path ID and must match the + * configured ID of a trusted path or the packet is discarded. + */ +#define ZT_PROTO_CIPHER_SUITE__NO_CRYPTO_TRUSTED_PATH 2 + +/** + * DEPRECATED payload encrypted flag, may be re-used in the future. + * + * This has been replaced by the three-bit cipher suite selection field. + */ +#define ZT_PROTO_FLAG_ENCRYPTED 0x80 + +/** + * Header flag indicating that a packet is fragmented + * + * If this flag is set, the receiver knows to expect more than one fragment. + * See Packet::Fragment for details. + */ +#define ZT_PROTO_FLAG_FRAGMENTED 0x40 + +/** + * Verb flag indicating payload is compressed with LZ4 + */ +#define ZT_PROTO_VERB_FLAG_COMPRESSED 0x80 + +/** + * Rounds used for Salsa20 encryption in ZT + * + * Discussion: + * + * DJB (Salsa20's designer) designed Salsa20 with a significant margin of 20 + * rounds, but has said repeatedly that 12 is likely sufficient. So far (as of + * July 2015) there are no published attacks against 12 rounds, let alone 20. + * + * In cryptography, a "break" means something different from what it means in + * common discussion. If a cipher is 256 bits strong and someone finds a way + * to reduce key search to 254 bits, this constitues a "break" in the academic + * literature. 254 bits is still far beyond what can be leveraged to accomplish + * a "break" as most people would understand it -- the actual decryption and + * reading of traffic. + * + * Nevertheless, "attacks only get better" as cryptographers like to say. As + * a result, they recommend not using anything that's shown any weakness even + * if that weakness is so far only meaningful to academics. It may be a sign + * of a deeper problem. + * + * So why choose a lower round count? + * + * Turns out the speed difference is nontrivial. On a Macbook Pro (Core i3) 20 + * rounds of SSE-optimized Salsa20 achieves ~508mb/sec/core, while 12 rounds + * hits ~832mb/sec/core. ZeroTier is designed for multiple objectives: + * security, simplicity, and performance. In this case a deference was made + * for performance. + * + * Meta discussion: + * + * The cipher is not the thing you should be paranoid about. + * + * I'll qualify that. If the cipher is known to be weak, like RC4, or has a + * key size that is too small, like DES, then yes you should worry about + * the cipher. + * + * But if the cipher is strong and your adversary is anyone other than the + * intelligence apparatus of a major superpower, you are fine in that + * department. + * + * Go ahead. Search for the last ten vulnerabilities discovered in SSL. Not + * a single one involved the breaking of a cipher. Now broaden your search. + * Look for issues with SSH, IPSec, etc. The only cipher-related issues you + * will find might involve the use of RC4 or MD5, algorithms with known + * issues or small key/digest sizes. But even weak ciphers are difficult to + * exploit in the real world -- you usually need a lot of data and a lot of + * compute time. No, virtually EVERY security vulnerability you will find + * involves a problem with the IMPLEMENTATION not with the cipher. + * + * A flaw in ZeroTier's protocol or code is incredibly, unbelievably + * more likely than a flaw in Salsa20 or any other cipher or cryptographic + * primitive it uses. We're talking odds of dying in a car wreck vs. odds of + * being personally impacted on the head by a meteorite. Nobody without a + * billion dollar budget is going to break into your network by actually + * cracking Salsa20/12 (or even /8) in the field. + * + * So stop worrying about the cipher unless you are, say, the Kremlin and your + * adversary is the NSA and the GCHQ. In that case... well that's above my + * pay grade. I'll just say defense in depth. + */ +#define ZT_PROTO_SALSA20_ROUNDS 12 + +/** + * PUSH_DIRECT_PATHS flag: forget path + */ +#define ZT_PUSH_DIRECT_PATHS_FLAG_FORGET_PATH 0x01 + +/** + * PUSH_DIRECT_PATHS flag: cluster redirect + */ +#define ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT 0x02 + +// Field indexes in packet header +#define ZT_PACKET_IDX_IV 0 +#define ZT_PACKET_IDX_DEST 8 +#define ZT_PACKET_IDX_SOURCE 13 +#define ZT_PACKET_IDX_FLAGS 18 +#define ZT_PACKET_IDX_MAC 19 +#define ZT_PACKET_IDX_VERB 27 +#define ZT_PACKET_IDX_PAYLOAD 28 + +/** + * Packet buffer size (can be changed) + * + * The current value is big enough for ZT_MAX_PACKET_FRAGMENTS, the pragmatic + * packet fragment limit, times the default UDP MTU. Most packets won't be + * this big. + */ +#define ZT_PROTO_MAX_PACKET_LENGTH (ZT_MAX_PACKET_FRAGMENTS * ZT_UDP_DEFAULT_PAYLOAD_MTU) + +/** + * Minimum viable packet length (a.k.a. header length) + */ +#define ZT_PROTO_MIN_PACKET_LENGTH ZT_PACKET_IDX_PAYLOAD + +// Indexes of fields in fragment header +#define ZT_PACKET_FRAGMENT_IDX_PACKET_ID 0 +#define ZT_PACKET_FRAGMENT_IDX_DEST 8 +#define ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR 13 +#define ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO 14 +#define ZT_PACKET_FRAGMENT_IDX_HOPS 15 +#define ZT_PACKET_FRAGMENT_IDX_PAYLOAD 16 + +/** + * Magic number found at ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR + */ +#define ZT_PACKET_FRAGMENT_INDICATOR ZT_ADDRESS_RESERVED_PREFIX + +/** + * Minimum viable fragment length + */ +#define ZT_PROTO_MIN_FRAGMENT_LENGTH ZT_PACKET_FRAGMENT_IDX_PAYLOAD + +// Field incides for parsing verbs ------------------------------------------- + +// Some verbs have variable-length fields. Those aren't fully defined here +// yet-- instead they are parsed using relative indexes in IncomingPacket. +// See their respective handler functions. + +#define ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION (ZT_PROTO_VERB_HELLO_IDX_PROTOCOL_VERSION + 1) +#define ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION (ZT_PROTO_VERB_HELLO_IDX_MAJOR_VERSION + 1) +#define ZT_PROTO_VERB_HELLO_IDX_REVISION (ZT_PROTO_VERB_HELLO_IDX_MINOR_VERSION + 1) +#define ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP (ZT_PROTO_VERB_HELLO_IDX_REVISION + 2) +#define ZT_PROTO_VERB_HELLO_IDX_IDENTITY (ZT_PROTO_VERB_HELLO_IDX_TIMESTAMP + 8) + +#define ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID (ZT_PROTO_VERB_ERROR_IDX_IN_RE_VERB + 1) +#define ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE (ZT_PROTO_VERB_ERROR_IDX_IN_RE_PACKET_ID + 8) +#define ZT_PROTO_VERB_ERROR_IDX_PAYLOAD (ZT_PROTO_VERB_ERROR_IDX_ERROR_CODE + 1) + +#define ZT_PROTO_VERB_OK_IDX_IN_RE_VERB (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID (ZT_PROTO_VERB_OK_IDX_IN_RE_VERB + 1) +#define ZT_PROTO_VERB_OK_IDX_PAYLOAD (ZT_PROTO_VERB_OK_IDX_IN_RE_PACKET_ID + 8) + +#define ZT_PROTO_VERB_WHOIS_IDX_ZTADDRESS (ZT_PACKET_IDX_PAYLOAD) + +#define ZT_PROTO_VERB_RENDEZVOUS_IDX_FLAGS (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS (ZT_PROTO_VERB_RENDEZVOUS_IDX_FLAGS + 1) +#define ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT (ZT_PROTO_VERB_RENDEZVOUS_IDX_ZTADDRESS + 5) +#define ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN (ZT_PROTO_VERB_RENDEZVOUS_IDX_PORT + 2) +#define ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRESS (ZT_PROTO_VERB_RENDEZVOUS_IDX_ADDRLEN + 1) + +#define ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_FRAME_IDX_NETWORK_ID + 8) +#define ZT_PROTO_VERB_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_FRAME_IDX_ETHERTYPE + 2) + +#define ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_EXT_FRAME_LEN_NETWORK_ID 8 +#define ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS (ZT_PROTO_VERB_EXT_FRAME_IDX_NETWORK_ID + ZT_PROTO_VERB_EXT_FRAME_LEN_NETWORK_ID) +#define ZT_PROTO_VERB_EXT_FRAME_LEN_FLAGS 1 +#define ZT_PROTO_VERB_EXT_FRAME_IDX_COM (ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS + ZT_PROTO_VERB_EXT_FRAME_LEN_FLAGS) +#define ZT_PROTO_VERB_EXT_FRAME_IDX_TO (ZT_PROTO_VERB_EXT_FRAME_IDX_FLAGS + ZT_PROTO_VERB_EXT_FRAME_LEN_FLAGS) +#define ZT_PROTO_VERB_EXT_FRAME_LEN_TO 6 +#define ZT_PROTO_VERB_EXT_FRAME_IDX_FROM (ZT_PROTO_VERB_EXT_FRAME_IDX_TO + ZT_PROTO_VERB_EXT_FRAME_LEN_TO) +#define ZT_PROTO_VERB_EXT_FRAME_LEN_FROM 6 +#define ZT_PROTO_VERB_EXT_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_EXT_FRAME_IDX_FROM + ZT_PROTO_VERB_EXT_FRAME_LEN_FROM) +#define ZT_PROTO_VERB_EXT_FRAME_LEN_ETHERTYPE 2 +#define ZT_PROTO_VERB_EXT_FRAME_IDX_PAYLOAD (ZT_PROTO_VERB_EXT_FRAME_IDX_ETHERTYPE + ZT_PROTO_VERB_EXT_FRAME_LEN_ETHERTYPE) + +#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_NETWORK_ID + 8) +#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST_IDX_DICT_LEN + 2) + +#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_FLAGS (ZT_PROTO_VERB_MULTICAST_GATHER_IDX_NETWORK_ID + 8) +#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC (ZT_PROTO_VERB_MULTICAST_GATHER_IDX_FLAGS + 1) +#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_ADI (ZT_PROTO_VERB_MULTICAST_GATHER_IDX_MAC + 6) +#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_GATHER_LIMIT (ZT_PROTO_VERB_MULTICAST_GATHER_IDX_ADI + 4) +#define ZT_PROTO_VERB_MULTICAST_GATHER_IDX_COM (ZT_PROTO_VERB_MULTICAST_GATHER_IDX_GATHER_LIMIT + 4) + +// Note: COM, GATHER_LIMIT, and SOURCE_MAC are optional, and so are specified without size +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID (ZT_PACKET_IDX_PAYLOAD) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_NETWORK_ID + 8) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_COM (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_GATHER_LIMIT (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_SOURCE_MAC (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_MAC (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FLAGS + 1) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_ADI (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_MAC + 6) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_DEST_ADI + 4) +#define ZT_PROTO_VERB_MULTICAST_FRAME_IDX_FRAME (ZT_PROTO_VERB_MULTICAST_FRAME_IDX_ETHERTYPE + 2) + +#define ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP (ZT_PROTO_VERB_OK_IDX_PAYLOAD) +#define ZT_PROTO_VERB_HELLO__OK__IDX_PROTOCOL_VERSION (ZT_PROTO_VERB_HELLO__OK__IDX_TIMESTAMP + 8) +#define ZT_PROTO_VERB_HELLO__OK__IDX_MAJOR_VERSION (ZT_PROTO_VERB_HELLO__OK__IDX_PROTOCOL_VERSION + 1) +#define ZT_PROTO_VERB_HELLO__OK__IDX_MINOR_VERSION (ZT_PROTO_VERB_HELLO__OK__IDX_MAJOR_VERSION + 1) +#define ZT_PROTO_VERB_HELLO__OK__IDX_REVISION (ZT_PROTO_VERB_HELLO__OK__IDX_MINOR_VERSION + 1) + +#define ZT_PROTO_VERB_WHOIS__OK__IDX_IDENTITY (ZT_PROTO_VERB_OK_IDX_PAYLOAD) + +#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_NETWORK_ID (ZT_PROTO_VERB_OK_IDX_PAYLOAD) +#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_NETWORK_ID + 8) +#define ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT (ZT_PROTO_VERB_NETWORK_CONFIG_REQUEST__OK__IDX_DICT_LEN + 2) + +#define ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID (ZT_PROTO_VERB_OK_IDX_PAYLOAD) +#define ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC (ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_NETWORK_ID + 8) +#define ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_ADI (ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_MAC + 6) +#define ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_GATHER_RESULTS (ZT_PROTO_VERB_MULTICAST_GATHER__OK__IDX_ADI + 4) + +#define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_NETWORK_ID (ZT_PROTO_VERB_OK_IDX_PAYLOAD) +#define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_MAC (ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_NETWORK_ID + 8) +#define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_ADI (ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_MAC + 6) +#define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_FLAGS (ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_ADI + 4) +#define ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_COM_AND_GATHER_RESULTS (ZT_PROTO_VERB_MULTICAST_FRAME__OK__IDX_FLAGS + 1) + +// --------------------------------------------------------------------------- + +namespace ZeroTier { + +/** + * ZeroTier packet + * + * Packet format: + * <[8] 64-bit packet ID / crypto IV / packet counter> + * <[5] destination ZT address> + * <[5] source ZT address> + * <[1] flags/cipher/hops> + * <[8] 64-bit MAC (or trusted path ID in trusted path mode)> + * [... -- begin encryption envelope -- ...] + * <[1] encrypted flags (MS 3 bits) and verb (LS 5 bits)> + * [... verb-specific payload ...] + * + * Packets smaller than 28 bytes are invalid and silently discarded. + * + * The 64-bit packet ID is a strongly random value used as a crypto IV. + * Its least significant 3 bits are also used as a monotonically increasing + * (and looping) counter for sending packets to a particular recipient. This + * can be used for link quality monitoring and reporting and has no crypto + * impact as it does not increase the likelihood of an IV collision. (The + * crypto we use is not sensitive to the nature of the IV, only that it does + * not repeat.) + * + * The flags/cipher/hops bit field is: FFCCCHHH where C is a 3-bit cipher + * selection allowing up to 7 cipher suites, F is outside-envelope flags, + * and H is hop count. + * + * The three-bit hop count is the only part of a packet that is mutable in + * transit without invalidating the MAC. All other bits in the packet are + * immutable. This is because intermediate nodes can increment the hop + * count up to 7 (protocol max). + * + * For unencrypted packets, MAC is computed on plaintext. Only HELLO is ever + * sent in the clear, as it's the "here is my public key" message. + */ +class Packet : public Buffer +{ +public: + /** + * A packet fragment + * + * Fragments are sent if a packet is larger than UDP MTU. The first fragment + * is sent with its normal header with the fragmented flag set. Remaining + * fragments are sent this way. + * + * The fragmented bit indicates that there is at least one fragment. Fragments + * themselves contain the total, so the receiver must "learn" this from the + * first fragment it receives. + * + * Fragments are sent with the following format: + * <[8] packet ID of packet whose fragment this belongs to> + * <[5] destination ZT address> + * <[1] 0xff, a reserved address, signals that this isn't a normal packet> + * <[1] total fragments (most significant 4 bits), fragment no (LS 4 bits)> + * <[1] ZT hop count (top 5 bits unused and must be zero)> + * <[...] fragment data> + * + * The protocol supports a maximum of 16 fragments. If a fragment is received + * before its main packet header, it should be cached for a brief period of + * time to see if its parent arrives. Loss of any fragment constitutes packet + * loss; there is no retransmission mechanism. The receiver must wait for full + * receipt to authenticate and decrypt; there is no per-fragment MAC. (But if + * fragments are corrupt, the MAC will fail for the whole assembled packet.) + */ + class Fragment : public Buffer + { + public: + Fragment() : + Buffer() + { + } + + template + Fragment(const Buffer &b) + throw(std::out_of_range) : + Buffer(b) + { + } + + Fragment(const void *data,unsigned int len) : + Buffer(data,len) + { + } + + /** + * Initialize from a packet + * + * @param p Original assembled packet + * @param fragStart Start of fragment (raw index in packet data) + * @param fragLen Length of fragment in bytes + * @param fragNo Which fragment (>= 1, since 0 is Packet with end chopped off) + * @param fragTotal Total number of fragments (including 0) + * @throws std::out_of_range Packet size would exceed buffer + */ + Fragment(const Packet &p,unsigned int fragStart,unsigned int fragLen,unsigned int fragNo,unsigned int fragTotal) + throw(std::out_of_range) + { + init(p,fragStart,fragLen,fragNo,fragTotal); + } + + /** + * Initialize from a packet + * + * @param p Original assembled packet + * @param fragStart Start of fragment (raw index in packet data) + * @param fragLen Length of fragment in bytes + * @param fragNo Which fragment (>= 1, since 0 is Packet with end chopped off) + * @param fragTotal Total number of fragments (including 0) + * @throws std::out_of_range Packet size would exceed buffer + */ + inline void init(const Packet &p,unsigned int fragStart,unsigned int fragLen,unsigned int fragNo,unsigned int fragTotal) + throw(std::out_of_range) + { + if ((fragStart + fragLen) > p.size()) + throw std::out_of_range("Packet::Fragment: tried to construct fragment of packet past its length"); + setSize(fragLen + ZT_PROTO_MIN_FRAGMENT_LENGTH); + + // NOTE: this copies both the IV/packet ID and the destination address. + memcpy(field(ZT_PACKET_FRAGMENT_IDX_PACKET_ID,13),p.field(ZT_PACKET_IDX_IV,13),13); + + (*this)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR] = ZT_PACKET_FRAGMENT_INDICATOR; + (*this)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO] = (char)(((fragTotal & 0xf) << 4) | (fragNo & 0xf)); + (*this)[ZT_PACKET_FRAGMENT_IDX_HOPS] = 0; + + memcpy(field(ZT_PACKET_FRAGMENT_IDX_PAYLOAD,fragLen),p.field(fragStart,fragLen),fragLen); + } + + /** + * Get this fragment's destination + * + * @return Destination ZT address + */ + inline Address destination() const { return Address(field(ZT_PACKET_FRAGMENT_IDX_DEST,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); } + + /** + * @return True if fragment is of a valid length + */ + inline bool lengthValid() const { return (size() >= ZT_PACKET_FRAGMENT_IDX_PAYLOAD); } + + /** + * @return ID of packet this is a fragment of + */ + inline uint64_t packetId() const { return at(ZT_PACKET_FRAGMENT_IDX_PACKET_ID); } + + /** + * @return Total number of fragments in packet + */ + inline unsigned int totalFragments() const { return (((unsigned int)((*this)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO]) >> 4) & 0xf); } + + /** + * @return Fragment number of this fragment + */ + inline unsigned int fragmentNumber() const { return ((unsigned int)((*this)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_NO]) & 0xf); } + + /** + * @return Fragment ZT hop count + */ + inline unsigned int hops() const { return (unsigned int)((*this)[ZT_PACKET_FRAGMENT_IDX_HOPS]); } + + /** + * Increment this packet's hop count + */ + inline void incrementHops() + { + (*this)[ZT_PACKET_FRAGMENT_IDX_HOPS] = (((*this)[ZT_PACKET_FRAGMENT_IDX_HOPS]) + 1) & ZT_PROTO_MAX_HOPS; + } + + /** + * @return Length of payload in bytes + */ + inline unsigned int payloadLength() const { return ((size() > ZT_PACKET_FRAGMENT_IDX_PAYLOAD) ? (size() - ZT_PACKET_FRAGMENT_IDX_PAYLOAD) : 0); } + + /** + * @return Raw packet payload + */ + inline const unsigned char *payload() const + { + return field(ZT_PACKET_FRAGMENT_IDX_PAYLOAD,size() - ZT_PACKET_FRAGMENT_IDX_PAYLOAD); + } + }; + + /** + * ZeroTier protocol verbs + */ + enum Verb /* Max value: 32 (5 bits) */ + { + /** + * No operation (ignored, no reply) + */ + VERB_NOP = 0x00, + + /** + * Announcement of a node's existence and vitals: + * <[1] protocol version> + * <[1] software major version> + * <[1] software minor version> + * <[2] software revision> + * <[8] timestamp for determining latency> + * <[...] binary serialized identity (see Identity)> + * <[...] physical destination address of packet> + * <[8] 64-bit world ID of current planet> + * <[8] 64-bit timestamp of current planet> + * [... remainder if packet is encrypted using cryptField() ...] + * <[2] 16-bit number of moons> + * [<[1] 8-bit type ID of moon>] + * [<[8] 64-bit world ID of moon>] + * [<[8] 64-bit timestamp of moon>] + * [... additional moon type/ID/timestamp tuples ...] + * <[2] 16-bit length of certificate of representation> + * [... certificate of representation ...] + * + * HELLO is sent in the clear as it is how peers share their identity + * public keys. A few additional fields are sent in the clear too, but + * these are things that are public info or are easy to determine. As + * of 1.2.0 we have added a few more fields, but since these could have + * the potential to be sensitive we introduced the encryption of the + * remainder of the packet. See cryptField(). Packet MAC is still + * performed of course, so authentication occurs as normal. + * + * Destination address is the actual wire address to which the packet + * was sent. See InetAddress::serialize() for format. + * + * OK payload: + * <[8] HELLO timestamp field echo> + * <[1] protocol version> + * <[1] software major version> + * <[1] software minor version> + * <[2] software revision> + * <[...] physical destination address of packet> + * <[2] 16-bit length of world update(s) or 0 if none> + * [[...] updates to planets and/or moons] + * <[2] 16-bit length of certificate of representation> + * [... certificate of representation ...] + * + * With the exception of the timestamp, the other fields pertain to the + * respondent who is sending OK and are not echoes. + * + * Note that OK is fully encrypted so no selective cryptField() of + * potentially sensitive fields is needed. + * + * ERROR has no payload. + */ + VERB_HELLO = 0x01, + + /** + * Error response: + * <[1] in-re verb> + * <[8] in-re packet ID> + * <[1] error code> + * <[...] error-dependent payload> + */ + VERB_ERROR = 0x02, + + /** + * Success response: + * <[1] in-re verb> + * <[8] in-re packet ID> + * <[...] request-specific payload> + */ + VERB_OK = 0x03, + + /** + * Query an identity by address: + * <[5] address to look up> + * [<[...] additional addresses to look up> + * + * OK response payload: + * <[...] binary serialized identity> + * [<[...] additional binary serialized identities>] + * + * If querying a cluster, duplicate OK responses may occasionally occur. + * These must be tolerated, which is easy since they'll have info you + * already have. + * + * If the address is not found, no response is generated. The semantics + * of WHOIS is similar to ARP and NDP in that persistent retrying can + * be performed. + */ + VERB_WHOIS = 0x04, + + /** + * Relay-mediated NAT traversal or firewall punching initiation: + * <[1] flags (unused, currently 0)> + * <[5] ZeroTier address of peer that might be found at this address> + * <[2] 16-bit protocol address port> + * <[1] protocol address length (4 for IPv4, 16 for IPv6)> + * <[...] protocol address (network byte order)> + * + * An upstream node can send this to inform both sides of a relay of + * information they might use to establish a direct connection. + * + * Upon receipt a peer sends HELLO to establish a direct link. + * + * No OK or ERROR is generated. + */ + VERB_RENDEZVOUS = 0x05, + + /** + * ZT-to-ZT unicast ethernet frame (shortened EXT_FRAME): + * <[8] 64-bit network ID> + * <[2] 16-bit ethertype> + * <[...] ethernet payload> + * + * MAC addresses are derived from the packet's source and destination + * ZeroTier addresses. This is a shortened EXT_FRAME that elides full + * Ethernet framing and other optional flags and features when they + * are not necessary. + * + * ERROR may be generated if a membership certificate is needed for a + * closed network. Payload will be network ID. + */ + VERB_FRAME = 0x06, + + /** + * Full Ethernet frame with MAC addressing and optional fields: + * <[8] 64-bit network ID> + * <[1] flags> + * <[6] destination MAC or all zero for destination node> + * <[6] source MAC or all zero for node of origin> + * <[2] 16-bit ethertype> + * <[...] ethernet payload> + * + * Flags: + * 0x01 - Certificate of network membership attached (DEPRECATED) + * 0x02 - Most significant bit of subtype (see below) + * 0x04 - Middle bit of subtype (see below) + * 0x08 - Least significant bit of subtype (see below) + * 0x10 - ACK requested in the form of OK(EXT_FRAME) + * + * Subtypes (0..7): + * 0x0 - Normal frame (bridging can be determined by checking MAC) + * 0x1 - TEEd outbound frame + * 0x2 - REDIRECTed outbound frame + * 0x3 - WATCHed outbound frame (TEE with ACK, ACK bit also set) + * 0x4 - TEEd inbound frame + * 0x5 - REDIRECTed inbound frame + * 0x6 - WATCHed inbound frame + * 0x7 - (reserved for future use) + * + * An extended frame carries full MAC addressing, making it a + * superset of VERB_FRAME. It is used for bridged traffic, + * redirected or observed traffic via rules, and can in theory + * be used for multicast though MULTICAST_FRAME exists for that + * purpose and has additional options and capabilities. + * + * OK payload (if ACK flag is set): + * <[8] 64-bit network ID> + */ + VERB_EXT_FRAME = 0x07, + + /** + * ECHO request (a.k.a. ping): + * <[...] arbitrary payload> + * + * This generates OK with a copy of the transmitted payload. No ERROR + * is generated. Response to ECHO requests is optional and ECHO may be + * ignored if a node detects a possible flood. + */ + VERB_ECHO = 0x08, + + /** + * Announce interest in multicast group(s): + * <[8] 64-bit network ID> + * <[6] multicast Ethernet address> + * <[4] multicast additional distinguishing information (ADI)> + * [... additional tuples of network/address/adi ...] + * + * LIKEs may be sent to any peer, though a good implementation should + * restrict them to peers on the same network they're for and to network + * controllers and root servers. In the current network, root servers + * will provide the service of final multicast cache. + * + * VERB_NETWORK_CREDENTIALS should be pushed along with this, especially + * if using upstream (e.g. root) nodes as multicast databases. This allows + * GATHERs to be authenticated. + * + * OK/ERROR are not generated. + */ + VERB_MULTICAST_LIKE = 0x09, + + /** + * Network credentials push: + * [<[...] one or more certificates of membership>] + * <[1] 0x00, null byte marking end of COM array> + * <[2] 16-bit number of capabilities> + * <[...] one or more serialized Capability> + * <[2] 16-bit number of tags> + * <[...] one or more serialized Tags> + * <[2] 16-bit number of revocations> + * <[...] one or more serialized Revocations> + * <[2] 16-bit number of certificates of ownership> + * <[...] one or more serialized CertificateOfOwnership> + * + * This can be sent by anyone at any time to push network credentials. + * These will of course only be accepted if they are properly signed. + * Credentials can be for any number of networks. + * + * The use of a zero byte to terminate the COM section is for legacy + * backward compatiblity. Newer fields are prefixed with a length. + * + * OK/ERROR are not generated. + */ + VERB_NETWORK_CREDENTIALS = 0x0a, + + /** + * Network configuration request: + * <[8] 64-bit network ID> + * <[2] 16-bit length of request meta-data dictionary> + * <[...] string-serialized request meta-data> + * <[8] 64-bit revision of netconf we currently have> + * <[8] 64-bit timestamp of netconf we currently have> + * + * This message requests network configuration from a node capable of + * providing it. + * + * Respones to this are always whole configs intended for the recipient. + * For patches and other updates a NETWORK_CONFIG is sent instead. + * + * It would be valid and correct as of 1.2.0 to use NETWORK_CONFIG always, + * but OK(NTEWORK_CONFIG_REQUEST) should be sent for compatibility. + * + * OK response payload: + * <[8] 64-bit network ID> + * <[2] 16-bit length of network configuration dictionary chunk> + * <[...] network configuration dictionary (may be incomplete)> + * [ ... end of legacy single chunk response ... ] + * <[1] 8-bit flags> + * <[8] 64-bit config update ID (should never be 0)> + * <[4] 32-bit total length of assembled dictionary> + * <[4] 32-bit index of chunk> + * [ ... end signed portion ... ] + * <[1] 8-bit chunk signature type> + * <[2] 16-bit length of chunk signature> + * <[...] chunk signature> + * + * The chunk signature signs the entire payload of the OK response. + * Currently only one signature type is supported: ed25519 (1). + * + * Each config chunk is signed to prevent memory exhaustion or + * traffic crowding DOS attacks against config fragment assembly. + * + * If the packet is from the network controller it is permitted to end + * before the config update ID or other chunking related or signature + * fields. This is to support older controllers that don't include + * these fields and may be removed in the future. + * + * ERROR response payload: + * <[8] 64-bit network ID> + */ + VERB_NETWORK_CONFIG_REQUEST = 0x0b, + + /** + * Network configuration data push: + * <[8] 64-bit network ID> + * <[2] 16-bit length of network configuration dictionary chunk> + * <[...] network configuration dictionary (may be incomplete)> + * <[1] 8-bit flags> + * <[8] 64-bit config update ID (should never be 0)> + * <[4] 32-bit total length of assembled dictionary> + * <[4] 32-bit index of chunk> + * [ ... end signed portion ... ] + * <[1] 8-bit chunk signature type> + * <[2] 16-bit length of chunk signature> + * <[...] chunk signature> + * + * This is a direct push variant for network config updates. It otherwise + * carries the same payload as OK(NETWORK_CONFIG_REQUEST) and has the same + * semantics. + * + * The legacy mode missing the additional chunking fields is not supported + * here. + * + * Flags: + * 0x01 - Use fast propagation + * + * An OK should be sent if the config is successfully received and + * accepted. + * + * OK payload: + * <[8] 64-bit network ID> + * <[8] 64-bit config update ID> + */ + VERB_NETWORK_CONFIG = 0x0c, + + /** + * Request endpoints for multicast distribution: + * <[8] 64-bit network ID> + * <[1] flags> + * <[6] MAC address of multicast group being queried> + * <[4] 32-bit ADI for multicast group being queried> + * <[4] 32-bit requested max number of multicast peers> + * [<[...] network certificate of membership>] + * + * Flags: + * 0x01 - COM is attached + * + * This message asks a peer for additional known endpoints that have + * LIKEd a given multicast group. It's sent when the sender wishes + * to send multicast but does not have the desired number of recipient + * peers. + * + * More than one OK response can occur if the response is broken up across + * multiple packets or if querying a clustered node. + * + * The COM should be included so that upstream nodes that are not + * members of our network can validate our request. + * + * OK response payload: + * <[8] 64-bit network ID> + * <[6] MAC address of multicast group being queried> + * <[4] 32-bit ADI for multicast group being queried> + * [begin gather results -- these same fields can be in OK(MULTICAST_FRAME)] + * <[4] 32-bit total number of known members in this multicast group> + * <[2] 16-bit number of members enumerated in this packet> + * <[...] series of 5-byte ZeroTier addresses of enumerated members> + * + * ERROR is not generated; queries that return no response are dropped. + */ + VERB_MULTICAST_GATHER = 0x0d, + + /** + * Multicast frame: + * <[8] 64-bit network ID> + * <[1] flags> + * [<[4] 32-bit implicit gather limit>] + * [<[6] source MAC>] + * <[6] destination MAC (multicast address)> + * <[4] 32-bit multicast ADI (multicast address extension)> + * <[2] 16-bit ethertype> + * <[...] ethernet payload> + * + * Flags: + * 0x01 - Network certificate of membership attached (DEPRECATED) + * 0x02 - Implicit gather limit field is present + * 0x04 - Source MAC is specified -- otherwise it's computed from sender + * + * OK and ERROR responses are optional. OK may be generated if there are + * implicit gather results or if the recipient wants to send its own + * updated certificate of network membership to the sender. ERROR may be + * generated if a certificate is needed or if multicasts to this group + * are no longer wanted (multicast unsubscribe). + * + * OK response payload: + * <[8] 64-bit network ID> + * <[6] MAC address of multicast group> + * <[4] 32-bit ADI for multicast group> + * <[1] flags> + * [<[...] network certficate of membership (DEPRECATED)>] + * [<[...] implicit gather results if flag 0x01 is set>] + * + * OK flags (same bits as request flags): + * 0x01 - OK includes certificate of network membership (DEPRECATED) + * 0x02 - OK includes implicit gather results + * + * ERROR response payload: + * <[8] 64-bit network ID> + * <[6] multicast group MAC> + * <[4] 32-bit multicast group ADI> + */ + VERB_MULTICAST_FRAME = 0x0e, + + /** + * Push of potential endpoints for direct communication: + * <[2] 16-bit number of paths> + * <[...] paths> + * + * Path record format: + * <[1] 8-bit path flags> + * <[2] length of extended path characteristics or 0 for none> + * <[...] extended path characteristics> + * <[1] address type> + * <[1] address length in bytes> + * <[...] address> + * + * Path record flags: + * 0x01 - Forget this path if currently known (not implemented yet) + * 0x02 - Cluster redirect -- use this in preference to others + * + * The receiver may, upon receiving a push, attempt to establish a + * direct link to one or more of the indicated addresses. It is the + * responsibility of the sender to limit which peers it pushes direct + * paths to to those with whom it has a trust relationship. The receiver + * must obey any restrictions provided such as exclusivity or blacklists. + * OK responses to this message are optional. + * + * Note that a direct path push does not imply that learned paths can't + * be used unless they are blacklisted explicitly or unless flag 0x01 + * is set. + * + * Only a subset of this functionality is currently implemented: basic + * path pushing and learning. Blacklisting and trust are not fully + * implemented yet (encryption is still always used). + * + * OK and ERROR are not generated. + */ + VERB_PUSH_DIRECT_PATHS = 0x10, + + /** + * Source-routed circuit test message: + * <[5] address of originator of circuit test> + * <[2] 16-bit flags> + * <[8] 64-bit timestamp> + * <[8] 64-bit test ID (arbitrary, set by tester)> + * <[2] 16-bit originator credential length (includes type)> + * [[1] originator credential type (for authorizing test)] + * [[...] originator credential] + * <[2] 16-bit length of additional fields> + * [[...] additional fields] + * [ ... end of signed portion of request ... ] + * <[2] 16-bit length of signature of request> + * <[...] signature of request by originator> + * <[2] 16-bit length of additional fields> + * [[...] additional fields] + * <[...] next hop(s) in path> + * + * Flags: + * 0x01 - Report back to originator at all hops + * 0x02 - Report back to originator at last hop + * + * Originator credential types: + * 0x01 - 64-bit network ID for which originator is controller + * + * Path record format: + * <[1] 8-bit flags (unused, must be zero)> + * <[1] 8-bit breadth (number of next hops)> + * <[...] one or more ZeroTier addresses of next hops> + * + * The circuit test allows a device to send a message that will traverse + * the network along a specified path, with each hop optionally reporting + * back to the tester via VERB_CIRCUIT_TEST_REPORT. + * + * Each circuit test packet includes a digital signature by the originator + * of the request, as well as a credential by which that originator claims + * authorization to perform the test. Currently this signature is ed25519, + * but in the future flags might be used to indicate an alternative + * algorithm. For example, the originator might be a network controller. + * In this case the test might be authorized if the recipient is a member + * of a network controlled by it, and if the previous hop(s) are also + * members. Each hop may include its certificate of network membership. + * + * Circuit test paths consist of a series of records. When a node receives + * an authorized circuit test, it: + * + * (1) Reports back to circuit tester as flags indicate + * (2) Reads and removes the next hop from the packet's path + * (3) Sends the packet along to next hop(s), if any. + * + * It is perfectly legal for a path to contain the same hop more than + * once. In fact, this can be a very useful test to determine if a hop + * can be reached bidirectionally and if so what that connectivity looks + * like. + * + * The breadth field in source-routed path records allows a hop to forward + * to more than one recipient, allowing the tester to specify different + * forms of graph traversal in a test. + * + * There is no hard limit to the number of hops in a test, but it is + * practically limited by the maximum size of a (possibly fragmented) + * ZeroTier packet. + * + * Support for circuit tests is optional. If they are not supported, the + * node should respond with an UNSUPPORTED_OPERATION error. If a circuit + * test request is not authorized, it may be ignored or reported as + * an INVALID_REQUEST. No OK messages are generated, but TEST_REPORT + * messages may be sent (see below). + * + * ERROR packet format: + * <[8] 64-bit timestamp (echoed from original> + * <[8] 64-bit test ID (echoed from original)> + */ + VERB_CIRCUIT_TEST = 0x11, + + /** + * Circuit test hop report: + * <[8] 64-bit timestamp (echoed from original test)> + * <[8] 64-bit test ID (echoed from original test)> + * <[8] 64-bit reserved field (set to 0, currently unused)> + * <[1] 8-bit vendor ID (set to 0, currently unused)> + * <[1] 8-bit reporter protocol version> + * <[1] 8-bit reporter software major version> + * <[1] 8-bit reporter software minor version> + * <[2] 16-bit reporter software revision> + * <[2] 16-bit reporter OS/platform or 0 if not specified> + * <[2] 16-bit reporter architecture or 0 if not specified> + * <[2] 16-bit error code (set to 0, currently unused)> + * <[8] 64-bit report flags> + * <[8] 64-bit packet ID of received CIRCUIT_TEST packet> + * <[5] upstream ZeroTier address from which CIRCUIT_TEST was received> + * <[1] 8-bit packet hop count of received CIRCUIT_TEST> + * <[...] local wire address on which packet was received> + * <[...] remote wire address from which packet was received> + * <[2] 16-bit path link quality of path over which packet was received> + * <[1] 8-bit number of next hops (breadth)> + * <[...] next hop information> + * + * Next hop information record format: + * <[5] ZeroTier address of next hop> + * <[...] current best direct path address, if any, 0 if none> + * + * Report flags: + * 0x1 - Upstream peer in circuit test path allowed in path (e.g. network COM valid) + * + * Circuit test reports can be sent by hops in a circuit test to report + * back results. They should include information about the sender as well + * as about the paths to which next hops are being sent. + * + * If a test report is received and no circuit test was sent, it should be + * ignored. This message generates no OK or ERROR response. + */ + VERB_CIRCUIT_TEST_REPORT = 0x12, + + /** + * A message with arbitrary user-definable content: + * <[8] 64-bit arbitrary message type ID> + * [<[...] message payload>] + * + * This can be used to send arbitrary messages over VL1. It generates no + * OK or ERROR and has no special semantics outside of whatever the user + * (via the ZeroTier core API) chooses to give it. + * + * Message type IDs less than or equal to 65535 are reserved for use by + * ZeroTier, Inc. itself. We recommend making up random ones for your own + * implementations. + */ + VERB_USER_MESSAGE = 0x14 + }; + + /** + * Error codes for VERB_ERROR + */ + enum ErrorCode + { + /* No error, not actually used in transit */ + ERROR_NONE = 0x00, + + /* Invalid request */ + ERROR_INVALID_REQUEST = 0x01, + + /* Bad/unsupported protocol version */ + ERROR_BAD_PROTOCOL_VERSION = 0x02, + + /* Unknown object queried */ + ERROR_OBJ_NOT_FOUND = 0x03, + + /* HELLO pushed an identity whose address is already claimed */ + ERROR_IDENTITY_COLLISION = 0x04, + + /* Verb or use case not supported/enabled by this node */ + ERROR_UNSUPPORTED_OPERATION = 0x05, + + /* Network membership certificate update needed */ + ERROR_NEED_MEMBERSHIP_CERTIFICATE = 0x06, + + /* Tried to join network, but you're not a member */ + ERROR_NETWORK_ACCESS_DENIED_ = 0x07, /* extra _ at end to avoid Windows name conflict */ + + /* Multicasts to this group are not wanted */ + ERROR_UNWANTED_MULTICAST = 0x08 + }; + +#ifdef ZT_TRACE + static const char *verbString(Verb v); + static const char *errorString(ErrorCode e); +#endif + + template + Packet(const Buffer &b) : + Buffer(b) + { + } + + Packet(const void *data,unsigned int len) : + Buffer(data,len) + { + } + + /** + * Construct a new empty packet with a unique random packet ID + * + * Flags and hops will be zero. Other fields and data region are undefined. + * Use the header access methods (setDestination() and friends) to fill out + * the header. Payload should be appended; initial size is header size. + */ + Packet() : + Buffer(ZT_PROTO_MIN_PACKET_LENGTH) + { + Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); + (*this)[ZT_PACKET_IDX_FLAGS] = 0; // zero flags, cipher ID, and hops + } + + /** + * Make a copy of a packet with a new initialization vector and destination address + * + * This can be used to take one draft prototype packet and quickly make copies to + * encrypt for different destinations. + * + * @param prototype Prototype packet + * @param dest Destination ZeroTier address for new packet + */ + Packet(const Packet &prototype,const Address &dest) : + Buffer(prototype) + { + Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); + setDestination(dest); + } + + /** + * Construct a new empty packet with a unique random packet ID + * + * @param dest Destination ZT address + * @param source Source ZT address + * @param v Verb + */ + Packet(const Address &dest,const Address &source,const Verb v) : + Buffer(ZT_PROTO_MIN_PACKET_LENGTH) + { + Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); + setDestination(dest); + setSource(source); + (*this)[ZT_PACKET_IDX_FLAGS] = 0; // zero flags and hops + setVerb(v); + } + + /** + * Reset this packet structure for reuse in place + * + * @param dest Destination ZT address + * @param source Source ZT address + * @param v Verb + */ + inline void reset(const Address &dest,const Address &source,const Verb v) + { + setSize(ZT_PROTO_MIN_PACKET_LENGTH); + Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); + setDestination(dest); + setSource(source); + (*this)[ZT_PACKET_IDX_FLAGS] = 0; // zero flags, cipher ID, and hops + setVerb(v); + } + + /** + * Generate a new IV / packet ID in place + * + * This can be used to re-use a packet buffer multiple times to send + * technically different but otherwise identical copies of the same + * packet. + */ + inline void newInitializationVector() { Utils::getSecureRandom(field(ZT_PACKET_IDX_IV,8),8); } + + /** + * Set this packet's destination + * + * @param dest ZeroTier address of destination + */ + inline void setDestination(const Address &dest) { dest.copyTo(field(ZT_PACKET_IDX_DEST,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); } + + /** + * Set this packet's source + * + * @param source ZeroTier address of source + */ + inline void setSource(const Address &source) { source.copyTo(field(ZT_PACKET_IDX_SOURCE,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); } + + /** + * Get this packet's destination + * + * @return Destination ZT address + */ + inline Address destination() const { return Address(field(ZT_PACKET_IDX_DEST,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); } + + /** + * Get this packet's source + * + * @return Source ZT address + */ + inline Address source() const { return Address(field(ZT_PACKET_IDX_SOURCE,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); } + + /** + * @return True if packet is of valid length + */ + inline bool lengthValid() const { return (size() >= ZT_PROTO_MIN_PACKET_LENGTH); } + + /** + * @return True if packet is fragmented (expect fragments) + */ + inline bool fragmented() const { return (((unsigned char)(*this)[ZT_PACKET_IDX_FLAGS] & ZT_PROTO_FLAG_FRAGMENTED) != 0); } + + /** + * Set this packet's fragmented flag + * + * @param f Fragmented flag value + */ + inline void setFragmented(bool f) + { + if (f) + (*this)[ZT_PACKET_IDX_FLAGS] |= (char)ZT_PROTO_FLAG_FRAGMENTED; + else (*this)[ZT_PACKET_IDX_FLAGS] &= (char)(~ZT_PROTO_FLAG_FRAGMENTED); + } + + /** + * @return True if compressed (result only valid if unencrypted) + */ + inline bool compressed() const { return (((unsigned char)(*this)[ZT_PACKET_IDX_VERB] & ZT_PROTO_VERB_FLAG_COMPRESSED) != 0); } + + /** + * @return ZeroTier forwarding hops (0 to 7) + */ + inline unsigned int hops() const { return ((unsigned int)(*this)[ZT_PACKET_IDX_FLAGS] & 0x07); } + + /** + * Increment this packet's hop count + */ + inline void incrementHops() + { + unsigned char &b = (*this)[ZT_PACKET_IDX_FLAGS]; + b = (b & 0xf8) | ((b + 1) & 0x07); + } + + /** + * @return Cipher suite selector: 0 - 7 (see #defines) + */ + inline unsigned int cipher() const + { + return (((unsigned int)(*this)[ZT_PACKET_IDX_FLAGS] & 0x38) >> 3); + } + + /** + * Set this packet's cipher suite + */ + inline void setCipher(unsigned int c) + { + unsigned char &b = (*this)[ZT_PACKET_IDX_FLAGS]; + b = (b & 0xc7) | (unsigned char)((c << 3) & 0x38); // bits: FFCCCHHH + // Set DEPRECATED "encrypted" flag -- used by pre-1.0.3 peers + if (c == ZT_PROTO_CIPHER_SUITE__C25519_POLY1305_SALSA2012) + b |= ZT_PROTO_FLAG_ENCRYPTED; + else b &= (~ZT_PROTO_FLAG_ENCRYPTED); + } + + /** + * Get the trusted path ID for this packet (only meaningful if cipher is trusted path) + * + * @return Trusted path ID (from MAC field) + */ + inline uint64_t trustedPathId() const { return at(ZT_PACKET_IDX_MAC); } + + /** + * Set this packet's trusted path ID and set the cipher spec to trusted path + * + * @param tpid Trusted path ID + */ + inline void setTrusted(const uint64_t tpid) + { + setCipher(ZT_PROTO_CIPHER_SUITE__NO_CRYPTO_TRUSTED_PATH); + setAt(ZT_PACKET_IDX_MAC,tpid); + } + + /** + * Get this packet's unique ID (the IV field interpreted as uint64_t) + * + * Note that the least significant 3 bits of this ID will change when armor() + * is called to armor the packet for transport. This is because armor() will + * mask the last 3 bits against the send counter for QoS monitoring use prior + * to actually using the IV to encrypt and MAC the packet. Be aware of this + * when grabbing the packetId of a new packet prior to armor/send. + * + * @return Packet ID + */ + inline uint64_t packetId() const { return at(ZT_PACKET_IDX_IV); } + + /** + * @return Value of link quality counter extracted from this packet's ID, range 0 to 7 (3 bits) + */ + inline unsigned int linkQualityCounter() const { return (unsigned int)(reinterpret_cast(data())[7] & 0x07); } + + /** + * Set packet verb + * + * This also has the side-effect of clearing any verb flags, such as + * compressed, and so must only be done during packet composition. + * + * @param v New packet verb + */ + inline void setVerb(Verb v) { (*this)[ZT_PACKET_IDX_VERB] = (char)v; } + + /** + * @return Packet verb (not including flag bits) + */ + inline Verb verb() const { return (Verb)((*this)[ZT_PACKET_IDX_VERB] & 0x1f); } + + /** + * @return Length of packet payload + */ + inline unsigned int payloadLength() const { return ((size() < ZT_PROTO_MIN_PACKET_LENGTH) ? 0 : (size() - ZT_PROTO_MIN_PACKET_LENGTH)); } + + /** + * @return Raw packet payload + */ + inline const unsigned char *payload() const { return field(ZT_PACKET_IDX_PAYLOAD,size() - ZT_PACKET_IDX_PAYLOAD); } + + /** + * Armor packet for transport + * + * @param key 32-byte key + * @param encryptPayload If true, encrypt packet payload, else just MAC + * @param counter Packet send counter for destination peer -- only least significant 3 bits are used + */ + void armor(const void *key,bool encryptPayload,unsigned int counter); + + /** + * Verify and (if encrypted) decrypt packet + * + * This does not handle trusted path mode packets and will return false + * for these. These are handled in IncomingPacket if the sending physical + * address and MAC field match a trusted path. + * + * @param key 32-byte key + * @return False if packet is invalid or failed MAC authenticity check + */ + bool dearmor(const void *key); + + /** + * Encrypt/decrypt a separately armored portion of a packet + * + * This currently uses Salsa20/12, but any message that uses this should + * incorporate a cipher selector to permit this to be changed later. To + * ensure that key stream is not reused, the key is slightly altered for + * this use case and the same initial 32 keystream bytes that are taken + * for MAC in ordinary armor() are also skipped here. + * + * This is currently only used to mask portions of HELLO as an extra + * security precation since most of that message is sent in the clear. + * + * This must NEVER be used more than once in the same packet, as doing + * so will result in re-use of the same key stream. + * + * @param key 32-byte key + * @param start Start of encrypted portion + * @param len Length of encrypted portion + */ + void cryptField(const void *key,unsigned int start,unsigned int len); + + /** + * Attempt to compress payload if not already (must be unencrypted) + * + * This requires that the payload at least contain the verb byte already + * set. The compressed flag in the verb is set if compression successfully + * results in a size reduction. If no size reduction occurs, compression + * is not done and the flag is left cleared. + * + * @return True if compression occurred + */ + bool compress(); + + /** + * Attempt to decompress payload if it is compressed (must be unencrypted) + * + * If payload is compressed, it is decompressed and the compressed verb + * flag is cleared. Otherwise nothing is done and true is returned. + * + * @return True if data is now decompressed and valid, false on error + */ + bool uncompress(); + +private: + static const unsigned char ZERO_KEY[32]; + + /** + * Deterministically mangle a 256-bit crypto key based on packet + * + * This uses extra data from the packet to mangle the secret, giving us an + * effective IV that is somewhat more than 64 bits. This is "free" for + * Salsa20 since it has negligible key setup time so using a different + * key each time is fine. + * + * @param in Input key (32 bytes) + * @param out Output buffer (32 bytes) + */ + inline void _salsa20MangleKey(const unsigned char *in,unsigned char *out) const + { + const unsigned char *d = (const unsigned char *)data(); + + // IV and source/destination addresses. Using the addresses divides the + // key space into two halves-- A->B and B->A (since order will change). + for(unsigned int i=0;i<18;++i) // 8 + (ZT_ADDRESS_LENGTH * 2) == 18 + out[i] = in[i] ^ d[i]; + + // Flags, but with hop count masked off. Hop count is altered by forwarding + // nodes. It's one of the only parts of a packet modifiable by people + // without the key. + out[18] = in[18] ^ (d[ZT_PACKET_IDX_FLAGS] & 0xf8); + + // Raw packet size in bytes -- thus each packet size defines a new + // key space. + out[19] = in[19] ^ (unsigned char)(size() & 0xff); + out[20] = in[20] ^ (unsigned char)((size() >> 8) & 0xff); // little endian + + // Rest of raw key is used unchanged + for(unsigned int i=21;i<32;++i) + out[i] = in[i]; + } +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Path.cpp b/node/Path.cpp new file mode 100644 index 0000000..7366b56 --- /dev/null +++ b/node/Path.cpp @@ -0,0 +1,34 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include "Path.hpp" +#include "RuntimeEnvironment.hpp" +#include "Node.hpp" + +namespace ZeroTier { + +bool Path::send(const RuntimeEnvironment *RR,void *tPtr,const void *data,unsigned int len,uint64_t now) +{ + if (RR->node->putPacket(tPtr,_localAddress,address(),data,len)) { + _lastOut = now; + return true; + } + return false; +} + +} // namespace ZeroTier diff --git a/node/Path.hpp b/node/Path.hpp new file mode 100644 index 0000000..aef628d --- /dev/null +++ b/node/Path.hpp @@ -0,0 +1,319 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_PATH_HPP +#define ZT_PATH_HPP + +#include +#include +#include + +#include +#include + +#include "Constants.hpp" +#include "InetAddress.hpp" +#include "SharedPtr.hpp" +#include "AtomicCounter.hpp" +#include "NonCopyable.hpp" +#include "Utils.hpp" + +/** + * Maximum return value of preferenceRank() + */ +#define ZT_PATH_MAX_PREFERENCE_RANK ((ZT_INETADDRESS_MAX_SCOPE << 1) | 1) + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * A path across the physical network + */ +class Path : NonCopyable +{ + friend class SharedPtr; + +public: + /** + * Efficient unique key for paths in a Hashtable + */ + class HashKey + { + public: + HashKey() {} + + HashKey(const InetAddress &l,const InetAddress &r) + { + // This is an ad-hoc bit packing algorithm to yield unique keys for + // remote addresses and their local-side counterparts if defined. + // Portability across runtimes is not needed. + if (r.ss_family == AF_INET) { + _k[0] = (uint64_t)reinterpret_cast(&r)->sin_addr.s_addr; + _k[1] = (uint64_t)reinterpret_cast(&r)->sin_port; + if (l.ss_family == AF_INET) { + _k[2] = (uint64_t)reinterpret_cast(&l)->sin_addr.s_addr; + _k[3] = (uint64_t)reinterpret_cast(&r)->sin_port; + } else { + _k[2] = 0; + _k[3] = 0; + } + } else if (r.ss_family == AF_INET6) { + const uint8_t *a = reinterpret_cast(reinterpret_cast(&r)->sin6_addr.s6_addr); + uint8_t *b = reinterpret_cast(_k); + for(unsigned int i=0;i<16;++i) b[i] = a[i]; + _k[2] = ~((uint64_t)reinterpret_cast(&r)->sin6_port); + if (l.ss_family == AF_INET6) { + _k[2] ^= ((uint64_t)reinterpret_cast(&r)->sin6_port) << 32; + a = reinterpret_cast(reinterpret_cast(&l)->sin6_addr.s6_addr); + b += 24; + for(unsigned int i=0;i<8;++i) b[i] = a[i]; + a += 8; + for(unsigned int i=0;i<8;++i) b[i] ^= a[i]; + } + } else { + _k[0] = 0; + _k[1] = 0; + _k[2] = 0; + _k[3] = 0; + } + } + + inline unsigned long hashCode() const { return (unsigned long)(_k[0] + _k[1] + _k[2] + _k[3]); } + + inline bool operator==(const HashKey &k) const { return ( (_k[0] == k._k[0]) && (_k[1] == k._k[1]) && (_k[2] == k._k[2]) && (_k[3] == k._k[3]) ); } + inline bool operator!=(const HashKey &k) const { return (!(*this == k)); } + + private: + uint64_t _k[4]; + }; + + Path() : + _lastOut(0), + _lastIn(0), + _lastTrustEstablishedPacketReceived(0), + _incomingLinkQualityFastLog(0xffffffffffffffffULL), + _incomingLinkQualitySlowLogPtr(0), + _incomingLinkQualitySlowLogCounter(-64), // discard first fast log + _incomingLinkQualityPreviousPacketCounter(0), + _outgoingPacketCounter(0), + _addr(), + _localAddress(), + _ipScope(InetAddress::IP_SCOPE_NONE) + { + for(int i=0;i<(int)sizeof(_incomingLinkQualitySlowLog);++i) + _incomingLinkQualitySlowLog[i] = ZT_PATH_LINK_QUALITY_MAX; + } + + Path(const InetAddress &localAddress,const InetAddress &addr) : + _lastOut(0), + _lastIn(0), + _lastTrustEstablishedPacketReceived(0), + _incomingLinkQualityFastLog(0xffffffffffffffffULL), + _incomingLinkQualitySlowLogPtr(0), + _incomingLinkQualitySlowLogCounter(-64), // discard first fast log + _incomingLinkQualityPreviousPacketCounter(0), + _outgoingPacketCounter(0), + _addr(addr), + _localAddress(localAddress), + _ipScope(addr.ipScope()) + { + for(int i=0;i<(int)sizeof(_incomingLinkQualitySlowLog);++i) + _incomingLinkQualitySlowLog[i] = ZT_PATH_LINK_QUALITY_MAX; + } + + /** + * Called when a packet is received from this remote path, regardless of content + * + * @param t Time of receive + */ + inline void received(const uint64_t t) { _lastIn = t; } + + /** + * Update link quality using a counter from an incoming packet (or packet head in fragmented case) + * + * @param counter Packet link quality counter (range 0 to 7, must not have other bits set) + */ + inline void updateLinkQuality(const unsigned int counter) + { + const unsigned int prev = _incomingLinkQualityPreviousPacketCounter; + _incomingLinkQualityPreviousPacketCounter = counter; + const uint64_t fl = (_incomingLinkQualityFastLog = ((_incomingLinkQualityFastLog << 1) | (uint64_t)(prev == ((counter - 1) & 0x7)))); + if (++_incomingLinkQualitySlowLogCounter >= 64) { + _incomingLinkQualitySlowLogCounter = 0; + _incomingLinkQualitySlowLog[_incomingLinkQualitySlowLogPtr++ % sizeof(_incomingLinkQualitySlowLog)] = (uint8_t)Utils::countBits(fl); + } + } + + /** + * @return Link quality from 0 (min) to 255 (max) + */ + inline unsigned int linkQuality() const + { + unsigned long slsize = _incomingLinkQualitySlowLogPtr; + if (slsize > (unsigned long)sizeof(_incomingLinkQualitySlowLog)) + slsize = (unsigned long)sizeof(_incomingLinkQualitySlowLog); + else if (!slsize) + return 255; // ZT_PATH_LINK_QUALITY_MAX + unsigned long lq = 0; + for(unsigned long i=0;i= 255) ? 255 : lq); + } + + /** + * Set time last trusted packet was received (done in Peer::received()) + */ + inline void trustedPacketReceived(const uint64_t t) { _lastTrustEstablishedPacketReceived = t; } + + /** + * Send a packet via this path (last out time is also updated) + * + * @param RR Runtime environment + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param data Packet data + * @param len Packet length + * @param now Current time + * @return True if transport reported success + */ + bool send(const RuntimeEnvironment *RR,void *tPtr,const void *data,unsigned int len,uint64_t now); + + /** + * Manually update last sent time + * + * @param t Time of send + */ + inline void sent(const uint64_t t) { _lastOut = t; } + + /** + * @return Address of local side of this path or NULL if unspecified + */ + inline const InetAddress &localAddress() const { return _localAddress; } + + /** + * @return Physical address + */ + inline const InetAddress &address() const { return _addr; } + + /** + * @return IP scope -- faster shortcut for address().ipScope() + */ + inline InetAddress::IpScope ipScope() const { return _ipScope; } + + /** + * @return True if path has received a trust established packet (e.g. common network membership) in the past ZT_TRUST_EXPIRATION ms + */ + inline bool trustEstablished(const uint64_t now) const { return ((now - _lastTrustEstablishedPacketReceived) < ZT_TRUST_EXPIRATION); } + + /** + * @return Preference rank, higher == better + */ + inline unsigned int preferenceRank() const + { + // This causes us to rank paths in order of IP scope rank (see InetAdddress.hpp) but + // within each IP scope class to prefer IPv6 over IPv4. + return ( ((unsigned int)_ipScope << 1) | (unsigned int)(_addr.ss_family == AF_INET6) ); + } + + /** + * Check whether this address is valid for a ZeroTier path + * + * This checks the address type and scope against address types and scopes + * that we currently support for ZeroTier communication. + * + * @param a Address to check + * @return True if address is good for ZeroTier path use + */ + static inline bool isAddressValidForPath(const InetAddress &a) + { + if ((a.ss_family == AF_INET)||(a.ss_family == AF_INET6)) { + switch(a.ipScope()) { + /* Note: we don't do link-local at the moment. Unfortunately these + * cause several issues. The first is that they usually require a + * device qualifier, which we don't handle yet and can't portably + * push in PUSH_DIRECT_PATHS. The second is that some OSes assign + * these very ephemerally or otherwise strangely. So we'll use + * private, pseudo-private, shared (e.g. carrier grade NAT), or + * global IP addresses. */ + case InetAddress::IP_SCOPE_PRIVATE: + case InetAddress::IP_SCOPE_PSEUDOPRIVATE: + case InetAddress::IP_SCOPE_SHARED: + case InetAddress::IP_SCOPE_GLOBAL: + if (a.ss_family == AF_INET6) { + // TEMPORARY HACK: for now, we are going to blacklist he.net IPv6 + // tunnels due to very spotty performance and low MTU issues over + // these IPv6 tunnel links. + const uint8_t *ipd = reinterpret_cast(reinterpret_cast(&a)->sin6_addr.s6_addr); + if ((ipd[0] == 0x20)&&(ipd[1] == 0x01)&&(ipd[2] == 0x04)&&(ipd[3] == 0x70)) + return false; + } + return true; + default: + return false; + } + } + return false; + } + + /** + * @return True if path appears alive + */ + inline bool alive(const uint64_t now) const { return ((now - _lastIn) <= ZT_PATH_ALIVE_TIMEOUT); } + + /** + * @return True if this path needs a heartbeat + */ + inline bool needsHeartbeat(const uint64_t now) const { return ((now - _lastOut) >= ZT_PATH_HEARTBEAT_PERIOD); } + + /** + * @return Last time we sent something + */ + inline uint64_t lastOut() const { return _lastOut; } + + /** + * @return Last time we received anything + */ + inline uint64_t lastIn() const { return _lastIn; } + + /** + * Return and increment outgoing packet counter (used with Packet::armor()) + * + * @return Next value that should be used for outgoing packet counter (only least significant 3 bits are used) + */ + inline unsigned int nextOutgoingCounter() { return _outgoingPacketCounter++; } + +private: + volatile uint64_t _lastOut; + volatile uint64_t _lastIn; + volatile uint64_t _lastTrustEstablishedPacketReceived; + volatile uint64_t _incomingLinkQualityFastLog; + volatile unsigned long _incomingLinkQualitySlowLogPtr; + volatile signed int _incomingLinkQualitySlowLogCounter; + volatile unsigned int _incomingLinkQualityPreviousPacketCounter; + volatile unsigned int _outgoingPacketCounter; + InetAddress _addr; + InetAddress _localAddress; + InetAddress::IpScope _ipScope; // memoize this since it's a computed value checked often + volatile uint8_t _incomingLinkQualitySlowLog[32]; + AtomicCounter __refCount; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Peer.cpp b/node/Peer.cpp new file mode 100644 index 0000000..2e9f6a2 --- /dev/null +++ b/node/Peer.cpp @@ -0,0 +1,435 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include "../version.h" + +#include "Constants.hpp" +#include "Peer.hpp" +#include "Node.hpp" +#include "Switch.hpp" +#include "Network.hpp" +#include "SelfAwareness.hpp" +#include "Cluster.hpp" +#include "Packet.hpp" + +namespace ZeroTier { + +Peer::Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity) : + RR(renv), + _lastReceive(0), + _lastNontrivialReceive(0), + _lastTriedMemorizedPath(0), + _lastDirectPathPushSent(0), + _lastDirectPathPushReceive(0), + _lastCredentialRequestSent(0), + _lastWhoisRequestReceived(0), + _lastEchoRequestReceived(0), + _lastComRequestReceived(0), + _lastComRequestSent(0), + _lastCredentialsReceived(0), + _lastTrustEstablishedPacketReceived(0), + _vProto(0), + _vMajor(0), + _vMinor(0), + _vRevision(0), + _id(peerIdentity), + _latency(0), + _directPathPushCutoffCount(0), + _credentialsCutoffCount(0) +{ + if (!myIdentity.agree(peerIdentity,_key,ZT_PEER_SECRET_KEY_LENGTH)) + throw std::runtime_error("new peer identity key agreement failed"); +} + +void Peer::received( + void *tPtr, + const SharedPtr &path, + const unsigned int hops, + const uint64_t packetId, + const Packet::Verb verb, + const uint64_t inRePacketId, + const Packet::Verb inReVerb, + const bool trustEstablished) +{ + const uint64_t now = RR->node->now(); + +#ifdef ZT_ENABLE_CLUSTER + bool isClusterSuboptimalPath = false; + if ((RR->cluster)&&(hops == 0)) { + // Note: findBetterEndpoint() is first since we still want to check + // for a better endpoint even if we don't actually send a redirect. + InetAddress redirectTo; + if ( (verb != Packet::VERB_OK) && (verb != Packet::VERB_ERROR) && (verb != Packet::VERB_RENDEZVOUS) && (verb != Packet::VERB_PUSH_DIRECT_PATHS) && (RR->cluster->findBetterEndpoint(redirectTo,_id.address(),path->address(),false)) ) { + if (_vProto >= 5) { + // For newer peers we can send a more idiomatic verb: PUSH_DIRECT_PATHS. + Packet outp(_id.address(),RR->identity.address(),Packet::VERB_PUSH_DIRECT_PATHS); + outp.append((uint16_t)1); // count == 1 + outp.append((uint8_t)ZT_PUSH_DIRECT_PATHS_FLAG_CLUSTER_REDIRECT); // flags: cluster redirect + outp.append((uint16_t)0); // no extensions + if (redirectTo.ss_family == AF_INET) { + outp.append((uint8_t)4); + outp.append((uint8_t)6); + outp.append(redirectTo.rawIpData(),4); + } else { + outp.append((uint8_t)6); + outp.append((uint8_t)18); + outp.append(redirectTo.rawIpData(),16); + } + outp.append((uint16_t)redirectTo.port()); + outp.armor(_key,true,path->nextOutgoingCounter()); + path->send(RR,tPtr,outp.data(),outp.size(),now); + } else { + // For older peers we use RENDEZVOUS to coax them into contacting us elsewhere. + Packet outp(_id.address(),RR->identity.address(),Packet::VERB_RENDEZVOUS); + outp.append((uint8_t)0); // no flags + RR->identity.address().appendTo(outp); + outp.append((uint16_t)redirectTo.port()); + if (redirectTo.ss_family == AF_INET) { + outp.append((uint8_t)4); + outp.append(redirectTo.rawIpData(),4); + } else { + outp.append((uint8_t)16); + outp.append(redirectTo.rawIpData(),16); + } + outp.armor(_key,true,path->nextOutgoingCounter()); + path->send(RR,tPtr,outp.data(),outp.size(),now); + } + isClusterSuboptimalPath = true; + } + } +#endif + + _lastReceive = now; + switch (verb) { + case Packet::VERB_FRAME: + case Packet::VERB_EXT_FRAME: + case Packet::VERB_NETWORK_CONFIG_REQUEST: + case Packet::VERB_NETWORK_CONFIG: + case Packet::VERB_MULTICAST_FRAME: + _lastNontrivialReceive = now; + break; + default: break; + } + + if (trustEstablished) { + _lastTrustEstablishedPacketReceived = now; + path->trustedPacketReceived(now); + } + + if (_vProto >= 9) + path->updateLinkQuality((unsigned int)(packetId & 7)); + + if (hops == 0) { + bool pathAlreadyKnown = false; + { + Mutex::Lock _l(_paths_m); + if ((path->address().ss_family == AF_INET)&&(_v4Path.p)) { + const struct sockaddr_in *const r = reinterpret_cast(&(path->address())); + const struct sockaddr_in *const l = reinterpret_cast(&(_v4Path.p->address())); + const struct sockaddr_in *const rl = reinterpret_cast(&(path->localAddress())); + const struct sockaddr_in *const ll = reinterpret_cast(&(_v4Path.p->localAddress())); + if ((r->sin_addr.s_addr == l->sin_addr.s_addr)&&(r->sin_port == l->sin_port)&&(rl->sin_addr.s_addr == ll->sin_addr.s_addr)&&(rl->sin_port == ll->sin_port)) { + _v4Path.lr = now; +#ifdef ZT_ENABLE_CLUSTER + _v4Path.localClusterSuboptimal = isClusterSuboptimalPath; +#endif + pathAlreadyKnown = true; + } + } else if ((path->address().ss_family == AF_INET6)&&(_v6Path.p)) { + const struct sockaddr_in6 *const r = reinterpret_cast(&(path->address())); + const struct sockaddr_in6 *const l = reinterpret_cast(&(_v6Path.p->address())); + const struct sockaddr_in6 *const rl = reinterpret_cast(&(path->localAddress())); + const struct sockaddr_in6 *const ll = reinterpret_cast(&(_v6Path.p->localAddress())); + if ((!memcmp(r->sin6_addr.s6_addr,l->sin6_addr.s6_addr,16))&&(r->sin6_port == l->sin6_port)&&(!memcmp(rl->sin6_addr.s6_addr,ll->sin6_addr.s6_addr,16))&&(rl->sin6_port == ll->sin6_port)) { + _v6Path.lr = now; +#ifdef ZT_ENABLE_CLUSTER + _v6Path.localClusterSuboptimal = isClusterSuboptimalPath; +#endif + pathAlreadyKnown = true; + } + } + } + + if ( (!pathAlreadyKnown) && (RR->node->shouldUsePathForZeroTierTraffic(tPtr,_id.address(),path->localAddress(),path->address())) ) { + Mutex::Lock _l(_paths_m); + _PeerPath *potentialNewPeerPath = (_PeerPath *)0; + if (path->address().ss_family == AF_INET) { + if ( (!_v4Path.p) || (!_v4Path.p->alive(now)) || ((_v4Path.p->address() != _v4ClusterPreferred)&&(path->preferenceRank() >= _v4Path.p->preferenceRank())) ) { + potentialNewPeerPath = &_v4Path; + } + } else if (path->address().ss_family == AF_INET6) { + if ( (!_v6Path.p) || (!_v6Path.p->alive(now)) || ((_v6Path.p->address() != _v6ClusterPreferred)&&(path->preferenceRank() >= _v6Path.p->preferenceRank())) ) { + potentialNewPeerPath = &_v6Path; + } + } + if (potentialNewPeerPath) { + if (verb == Packet::VERB_OK) { + potentialNewPeerPath->lr = now; + potentialNewPeerPath->p = path; +#ifdef ZT_ENABLE_CLUSTER + potentialNewPeerPath->localClusterSuboptimal = isClusterSuboptimalPath; + if (RR->cluster) + RR->cluster->broadcastHavePeer(_id); +#endif + } else { + TRACE("got %s via unknown path %s(%s), confirming...",Packet::verbString(verb),_id.address().toString().c_str(),path->address().toString().c_str()); + attemptToContactAt(tPtr,path->localAddress(),path->address(),now,true,path->nextOutgoingCounter()); + path->sent(now); + } + } + } + } else if (this->trustEstablished(now)) { + // Send PUSH_DIRECT_PATHS if hops>0 (relayed) and we have a trust relationship (common network membership) +#ifdef ZT_ENABLE_CLUSTER + // Cluster mode disables normal PUSH_DIRECT_PATHS in favor of cluster-based peer redirection + const bool haveCluster = (RR->cluster); +#else + const bool haveCluster = false; +#endif + if ( ((now - _lastDirectPathPushSent) >= ZT_DIRECT_PATH_PUSH_INTERVAL) && (!haveCluster) ) { + _lastDirectPathPushSent = now; + + std::vector pathsToPush; + + std::vector dps(RR->node->directPaths()); + for(std::vector::const_iterator i(dps.begin());i!=dps.end();++i) + pathsToPush.push_back(*i); + + std::vector sym(RR->sa->getSymmetricNatPredictions()); + for(unsigned long i=0,added=0;inode->prng() % sym.size()]); + if (std::find(pathsToPush.begin(),pathsToPush.end(),tmp) == pathsToPush.end()) { + pathsToPush.push_back(tmp); + if (++added >= ZT_PUSH_DIRECT_PATHS_MAX_PER_SCOPE_AND_FAMILY) + break; + } + } + + if (pathsToPush.size() > 0) { +#ifdef ZT_TRACE + std::string ps; + for(std::vector::const_iterator p(pathsToPush.begin());p!=pathsToPush.end();++p) { + if (ps.length() > 0) + ps.push_back(','); + ps.append(p->toString()); + } + TRACE("pushing %u direct paths to %s: %s",(unsigned int)pathsToPush.size(),_id.address().toString().c_str(),ps.c_str()); +#endif + + std::vector::const_iterator p(pathsToPush.begin()); + while (p != pathsToPush.end()) { + Packet outp(_id.address(),RR->identity.address(),Packet::VERB_PUSH_DIRECT_PATHS); + outp.addSize(2); // leave room for count + + unsigned int count = 0; + while ((p != pathsToPush.end())&&((outp.size() + 24) < 1200)) { + uint8_t addressType = 4; + switch(p->ss_family) { + case AF_INET: + break; + case AF_INET6: + addressType = 6; + break; + default: // we currently only push IP addresses + ++p; + continue; + } + + outp.append((uint8_t)0); // no flags + outp.append((uint16_t)0); // no extensions + outp.append(addressType); + outp.append((uint8_t)((addressType == 4) ? 6 : 18)); + outp.append(p->rawIpData(),((addressType == 4) ? 4 : 16)); + outp.append((uint16_t)p->port()); + + ++count; + ++p; + } + + if (count) { + outp.setAt(ZT_PACKET_IDX_PAYLOAD,(uint16_t)count); + outp.armor(_key,true,path->nextOutgoingCounter()); + path->send(RR,tPtr,outp.data(),outp.size(),now); + } + } + } + } + } +} + +bool Peer::sendDirect(void *tPtr,const void *data,unsigned int len,uint64_t now,bool force) +{ + Mutex::Lock _l(_paths_m); + + uint64_t v6lr = 0; + if ( ((now - _v6Path.lr) < ZT_PEER_PATH_EXPIRATION) && (_v6Path.p) ) + v6lr = _v6Path.p->lastIn(); + uint64_t v4lr = 0; + if ( ((now - _v4Path.lr) < ZT_PEER_PATH_EXPIRATION) && (_v4Path.p) ) + v4lr = _v4Path.p->lastIn(); + + if ( (v6lr > v4lr) && ((now - v6lr) < ZT_PATH_ALIVE_TIMEOUT) ) { + return _v6Path.p->send(RR,tPtr,data,len,now); + } else if ((now - v4lr) < ZT_PATH_ALIVE_TIMEOUT) { + return _v4Path.p->send(RR,tPtr,data,len,now); + } else if (force) { + if (v6lr > v4lr) { + return _v6Path.p->send(RR,tPtr,data,len,now); + } else if (v4lr) { + return _v4Path.p->send(RR,tPtr,data,len,now); + } + } + + return false; +} + +SharedPtr Peer::getBestPath(uint64_t now,bool includeExpired) +{ + Mutex::Lock _l(_paths_m); + + uint64_t v6lr = 0; + if ( ( includeExpired || ((now - _v6Path.lr) < ZT_PEER_PATH_EXPIRATION) ) && (_v6Path.p) ) + v6lr = _v6Path.p->lastIn(); + uint64_t v4lr = 0; + if ( ( includeExpired || ((now - _v4Path.lr) < ZT_PEER_PATH_EXPIRATION) ) && (_v4Path.p) ) + v4lr = _v4Path.p->lastIn(); + + if (v6lr > v4lr) { + return _v6Path.p; + } else if (v4lr) { + return _v4Path.p; + } + + return SharedPtr(); +} + +void Peer::sendHELLO(void *tPtr,const InetAddress &localAddr,const InetAddress &atAddress,uint64_t now,unsigned int counter) +{ + Packet outp(_id.address(),RR->identity.address(),Packet::VERB_HELLO); + + outp.append((unsigned char)ZT_PROTO_VERSION); + outp.append((unsigned char)ZEROTIER_ONE_VERSION_MAJOR); + outp.append((unsigned char)ZEROTIER_ONE_VERSION_MINOR); + outp.append((uint16_t)ZEROTIER_ONE_VERSION_REVISION); + outp.append(now); + RR->identity.serialize(outp,false); + atAddress.serialize(outp); + + outp.append((uint64_t)RR->topology->planetWorldId()); + outp.append((uint64_t)RR->topology->planetWorldTimestamp()); + + const unsigned int startCryptedPortionAt = outp.size(); + + std::vector moons(RR->topology->moons()); + std::vector moonsWanted(RR->topology->moonsWanted()); + outp.append((uint16_t)(moons.size() + moonsWanted.size())); + for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { + outp.append((uint8_t)m->type()); + outp.append((uint64_t)m->id()); + outp.append((uint64_t)m->timestamp()); + } + for(std::vector::const_iterator m(moonsWanted.begin());m!=moonsWanted.end();++m) { + outp.append((uint8_t)World::TYPE_MOON); + outp.append(*m); + outp.append((uint64_t)0); + } + + const unsigned int corSizeAt = outp.size(); + outp.addSize(2); + RR->topology->appendCertificateOfRepresentation(outp); + outp.setAt(corSizeAt,(uint16_t)(outp.size() - (corSizeAt + 2))); + + outp.cryptField(_key,startCryptedPortionAt,outp.size() - startCryptedPortionAt); + + RR->node->expectReplyTo(outp.packetId()); + + if (atAddress) { + outp.armor(_key,false,counter); // false == don't encrypt full payload, but add MAC + RR->node->putPacket(tPtr,localAddr,atAddress,outp.data(),outp.size()); + } else { + RR->sw->send(tPtr,outp,false); // false == don't encrypt full payload, but add MAC + } +} + +void Peer::attemptToContactAt(void *tPtr,const InetAddress &localAddr,const InetAddress &atAddress,uint64_t now,bool sendFullHello,unsigned int counter) +{ + if ( (!sendFullHello) && (_vProto >= 5) && (!((_vMajor == 1)&&(_vMinor == 1)&&(_vRevision == 0))) ) { + Packet outp(_id.address(),RR->identity.address(),Packet::VERB_ECHO); + RR->node->expectReplyTo(outp.packetId()); + outp.armor(_key,true,counter); + RR->node->putPacket(tPtr,localAddr,atAddress,outp.data(),outp.size()); + } else { + sendHELLO(tPtr,localAddr,atAddress,now,counter); + } +} + +void Peer::tryMemorizedPath(void *tPtr,uint64_t now) +{ + if ((now - _lastTriedMemorizedPath) >= ZT_TRY_MEMORIZED_PATH_INTERVAL) { + _lastTriedMemorizedPath = now; + InetAddress mp; + if (RR->node->externalPathLookup(tPtr,_id.address(),-1,mp)) + attemptToContactAt(tPtr,InetAddress(),mp,now,true,0); + } +} + +bool Peer::doPingAndKeepalive(void *tPtr,uint64_t now,int inetAddressFamily) +{ + Mutex::Lock _l(_paths_m); + + if (inetAddressFamily < 0) { + uint64_t v6lr = 0; + if ( ((now - _v6Path.lr) < ZT_PEER_PATH_EXPIRATION) && (_v6Path.p) ) + v6lr = _v6Path.p->lastIn(); + uint64_t v4lr = 0; + if ( ((now - _v4Path.lr) < ZT_PEER_PATH_EXPIRATION) && (_v4Path.p) ) + v4lr = _v4Path.p->lastIn(); + + if (v6lr > v4lr) { + if ( ((now - _v6Path.lr) >= ZT_PEER_PING_PERIOD) || (_v6Path.p->needsHeartbeat(now)) ) { + attemptToContactAt(tPtr,_v6Path.p->localAddress(),_v6Path.p->address(),now,false,_v6Path.p->nextOutgoingCounter()); + _v6Path.p->sent(now); + return true; + } + } else if (v4lr) { + if ( ((now - _v4Path.lr) >= ZT_PEER_PING_PERIOD) || (_v4Path.p->needsHeartbeat(now)) ) { + attemptToContactAt(tPtr,_v4Path.p->localAddress(),_v4Path.p->address(),now,false,_v4Path.p->nextOutgoingCounter()); + _v4Path.p->sent(now); + return true; + } + } + } else { + if ( (inetAddressFamily == AF_INET) && ((now - _v4Path.lr) < ZT_PEER_PATH_EXPIRATION) ) { + if ( ((now - _v4Path.lr) >= ZT_PEER_PING_PERIOD) || (_v4Path.p->needsHeartbeat(now)) ) { + attemptToContactAt(tPtr,_v4Path.p->localAddress(),_v4Path.p->address(),now,false,_v4Path.p->nextOutgoingCounter()); + _v4Path.p->sent(now); + return true; + } + } else if ( (inetAddressFamily == AF_INET6) && ((now - _v6Path.lr) < ZT_PEER_PATH_EXPIRATION) ) { + if ( ((now - _v6Path.lr) >= ZT_PEER_PING_PERIOD) || (_v6Path.p->needsHeartbeat(now)) ) { + attemptToContactAt(tPtr,_v6Path.p->localAddress(),_v6Path.p->address(),now,false,_v6Path.p->nextOutgoingCounter()); + _v6Path.p->sent(now); + return true; + } + } + } + + return false; +} + +} // namespace ZeroTier diff --git a/node/Peer.hpp b/node/Peer.hpp new file mode 100644 index 0000000..b9d8540 --- /dev/null +++ b/node/Peer.hpp @@ -0,0 +1,505 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_PEER_HPP +#define ZT_PEER_HPP + +#include + +#include "Constants.hpp" + +#include +#include +#include +#include + +#include "../include/ZeroTierOne.h" + +#include "RuntimeEnvironment.hpp" +#include "Path.hpp" +#include "Address.hpp" +#include "Utils.hpp" +#include "Identity.hpp" +#include "InetAddress.hpp" +#include "Packet.hpp" +#include "SharedPtr.hpp" +#include "AtomicCounter.hpp" +#include "Hashtable.hpp" +#include "Mutex.hpp" +#include "NonCopyable.hpp" + +namespace ZeroTier { + +/** + * Peer on P2P Network (virtual layer 1) + */ +class Peer : NonCopyable +{ + friend class SharedPtr; + +private: + Peer() {} // disabled to prevent bugs -- should not be constructed uninitialized + +public: + ~Peer() { Utils::burn(_key,sizeof(_key)); } + + /** + * Construct a new peer + * + * @param renv Runtime environment + * @param myIdentity Identity of THIS node (for key agreement) + * @param peerIdentity Identity of peer + * @throws std::runtime_error Key agreement with peer's identity failed + */ + Peer(const RuntimeEnvironment *renv,const Identity &myIdentity,const Identity &peerIdentity); + + /** + * @return This peer's ZT address (short for identity().address()) + */ + inline const Address &address() const throw() { return _id.address(); } + + /** + * @return This peer's identity + */ + inline const Identity &identity() const throw() { return _id; } + + /** + * Log receipt of an authenticated packet + * + * This is called by the decode pipe when a packet is proven to be authentic + * and appears to be valid. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param path Path over which packet was received + * @param hops ZeroTier (not IP) hops + * @param packetId Packet ID + * @param verb Packet verb + * @param inRePacketId Packet ID in reply to (default: none) + * @param inReVerb Verb in reply to (for OK/ERROR, default: VERB_NOP) + * @param trustEstablished If true, some form of non-trivial trust (like allowed in network) has been established + */ + void received( + void *tPtr, + const SharedPtr &path, + const unsigned int hops, + const uint64_t packetId, + const Packet::Verb verb, + const uint64_t inRePacketId, + const Packet::Verb inReVerb, + const bool trustEstablished); + + /** + * @param now Current time + * @param addr Remote address + * @return True if we have an active path to this destination + */ + inline bool hasActivePathTo(uint64_t now,const InetAddress &addr) const + { + Mutex::Lock _l(_paths_m); + return ( ((addr.ss_family == AF_INET)&&(_v4Path.p)&&(_v4Path.p->address() == addr)&&(_v4Path.p->alive(now))) || ((addr.ss_family == AF_INET6)&&(_v6Path.p)&&(_v6Path.p->address() == addr)&&(_v6Path.p->alive(now))) ); + } + + /** + * Send via best direct path + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param data Packet data + * @param len Packet length + * @param now Current time + * @param force If true, send even if path is not alive + * @return True if we actually sent something + */ + bool sendDirect(void *tPtr,const void *data,unsigned int len,uint64_t now,bool force); + + /** + * Get the best current direct path + * + * This does not check Path::alive(), but does return the most recently + * active path and does check expiration (which is a longer timeout). + * + * @param now Current time + * @param includeExpired If true, include even expired paths + * @return Best current path or NULL if none + */ + SharedPtr getBestPath(uint64_t now,bool includeExpired); + + /** + * Send a HELLO to this peer at a specified physical address + * + * No statistics or sent times are updated here. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param localAddr Local address + * @param atAddress Destination address + * @param now Current time + * @param counter Outgoing packet counter + */ + void sendHELLO(void *tPtr,const InetAddress &localAddr,const InetAddress &atAddress,uint64_t now,unsigned int counter); + + /** + * Send ECHO (or HELLO for older peers) to this peer at the given address + * + * No statistics or sent times are updated here. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param localAddr Local address + * @param atAddress Destination address + * @param now Current time + * @param sendFullHello If true, always send a full HELLO instead of just an ECHO + * @param counter Outgoing packet counter + */ + void attemptToContactAt(void *tPtr,const InetAddress &localAddr,const InetAddress &atAddress,uint64_t now,bool sendFullHello,unsigned int counter); + + /** + * Try a memorized or statically defined path if any are known + * + * Under the hood this is done periodically based on ZT_TRY_MEMORIZED_PATH_INTERVAL. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param now Current time + */ + void tryMemorizedPath(void *tPtr,uint64_t now); + + /** + * Send pings or keepalives depending on configured timeouts + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param now Current time + * @param inetAddressFamily Keep this address family alive, or -1 for any + * @return True if we have at least one direct path of the given family (or any if family is -1) + */ + bool doPingAndKeepalive(void *tPtr,uint64_t now,int inetAddressFamily); + + /** + * Reset paths within a given IP scope and address family + * + * Resetting a path involves sending an ECHO to it and then deactivating + * it until or unless it responds. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param scope IP scope + * @param inetAddressFamily Family e.g. AF_INET + * @param now Current time + */ + inline void resetWithinScope(void *tPtr,InetAddress::IpScope scope,int inetAddressFamily,uint64_t now) + { + Mutex::Lock _l(_paths_m); + if ((inetAddressFamily == AF_INET)&&(_v4Path.lr)&&(_v4Path.p->address().ipScope() == scope)) { + attemptToContactAt(tPtr,_v4Path.p->localAddress(),_v4Path.p->address(),now,false,_v4Path.p->nextOutgoingCounter()); + _v4Path.p->sent(now); + _v4Path.lr = 0; // path will not be used unless it speaks again + } else if ((inetAddressFamily == AF_INET6)&&(_v6Path.lr)&&(_v6Path.p->address().ipScope() == scope)) { + attemptToContactAt(tPtr,_v6Path.p->localAddress(),_v6Path.p->address(),now,false,_v6Path.p->nextOutgoingCounter()); + _v6Path.p->sent(now); + _v6Path.lr = 0; // path will not be used unless it speaks again + } + } + + /** + * Indicate that the given address was provided by a cluster as a preferred destination + * + * @param addr Address cluster prefers that we use + */ + inline void setClusterPreferred(const InetAddress &addr) + { + if (addr.ss_family == AF_INET) + _v4ClusterPreferred = addr; + else if (addr.ss_family == AF_INET6) + _v6ClusterPreferred = addr; + } + + /** + * Fill parameters with V4 and V6 addresses if known and alive + * + * @param now Current time + * @param v4 Result parameter to receive active IPv4 address, if any + * @param v6 Result parameter to receive active IPv6 address, if any + */ + inline void getRendezvousAddresses(uint64_t now,InetAddress &v4,InetAddress &v6) const + { + Mutex::Lock _l(_paths_m); + if (((now - _v4Path.lr) < ZT_PEER_PATH_EXPIRATION)&&(_v4Path.p->alive(now))) + v4 = _v4Path.p->address(); + if (((now - _v6Path.lr) < ZT_PEER_PATH_EXPIRATION)&&(_v6Path.p->alive(now))) + v6 = _v6Path.p->address(); + } + + /** + * @param now Current time + * @return All known paths to this peer + */ + inline std::vector< SharedPtr > paths(const uint64_t now) const + { + std::vector< SharedPtr > pp; + Mutex::Lock _l(_paths_m); + if (((now - _v4Path.lr) < ZT_PEER_PATH_EXPIRATION)&&(_v4Path.p->alive(now))) + pp.push_back(_v4Path.p); + if (((now - _v6Path.lr) < ZT_PEER_PATH_EXPIRATION)&&(_v6Path.p->alive(now))) + pp.push_back(_v6Path.p); + return pp; + } + + /** + * @return Time of last receive of anything, whether direct or relayed + */ + inline uint64_t lastReceive() const { return _lastReceive; } + + /** + * @return True if we've heard from this peer in less than ZT_PEER_ACTIVITY_TIMEOUT + */ + inline bool isAlive(const uint64_t now) const { return ((now - _lastReceive) < ZT_PEER_ACTIVITY_TIMEOUT); } + + /** + * @return True if this peer has sent us real network traffic recently + */ + inline uint64_t isActive(uint64_t now) const { return ((now - _lastNontrivialReceive) < ZT_PEER_ACTIVITY_TIMEOUT); } + + /** + * @return Latency in milliseconds or 0 if unknown + */ + inline unsigned int latency() const { return _latency; } + + /** + * This computes a quality score for relays and root servers + * + * If we haven't heard anything from these in ZT_PEER_ACTIVITY_TIMEOUT, they + * receive the worst possible quality (max unsigned int). Otherwise the + * quality is a product of latency and the number of potential missed + * pings. This causes roots and relays to switch over a bit faster if they + * fail. + * + * @return Relay quality score computed from latency and other factors, lower is better + */ + inline unsigned int relayQuality(const uint64_t now) const + { + const uint64_t tsr = now - _lastReceive; + if (tsr >= ZT_PEER_ACTIVITY_TIMEOUT) + return (~(unsigned int)0); + unsigned int l = _latency; + if (!l) + l = 0xffff; + return (l * (((unsigned int)tsr / (ZT_PEER_PING_PERIOD + 1000)) + 1)); + } + + /** + * Update latency with a new direct measurment + * + * @param l Direct latency measurment in ms + */ + inline void addDirectLatencyMeasurment(unsigned int l) + { + unsigned int ol = _latency; + if ((ol > 0)&&(ol < 10000)) + _latency = (ol + std::min(l,(unsigned int)65535)) / 2; + else _latency = std::min(l,(unsigned int)65535); + } + +#ifdef ZT_ENABLE_CLUSTER + /** + * @param now Current time + * @return True if this peer has at least one active direct path that is not cluster-suboptimal + */ + inline bool hasLocalClusterOptimalPath(uint64_t now) const + { + Mutex::Lock _l(_paths_m); + return ( ((_v4Path.p)&&(_v4Path.p->alive(now))&&(!_v4Path.localClusterSuboptimal)) || ((_v6Path.p)&&(_v6Path.p->alive(now))&&(!_v6Path.localClusterSuboptimal)) ); + } +#endif + + /** + * @return 256-bit secret symmetric encryption key + */ + inline const unsigned char *key() const { return _key; } + + /** + * Set the currently known remote version of this peer's client + * + * @param vproto Protocol version + * @param vmaj Major version + * @param vmin Minor version + * @param vrev Revision + */ + inline void setRemoteVersion(unsigned int vproto,unsigned int vmaj,unsigned int vmin,unsigned int vrev) + { + _vProto = (uint16_t)vproto; + _vMajor = (uint16_t)vmaj; + _vMinor = (uint16_t)vmin; + _vRevision = (uint16_t)vrev; + } + + inline unsigned int remoteVersionProtocol() const { return _vProto; } + inline unsigned int remoteVersionMajor() const { return _vMajor; } + inline unsigned int remoteVersionMinor() const { return _vMinor; } + inline unsigned int remoteVersionRevision() const { return _vRevision; } + + inline bool remoteVersionKnown() const { return ((_vMajor > 0)||(_vMinor > 0)||(_vRevision > 0)); } + + /** + * @return True if peer has received a trust established packet (e.g. common network membership) in the past ZT_TRUST_EXPIRATION ms + */ + inline bool trustEstablished(const uint64_t now) const { return ((now - _lastTrustEstablishedPacketReceived) < ZT_TRUST_EXPIRATION); } + + /** + * Rate limit gate for VERB_PUSH_DIRECT_PATHS + */ + inline bool rateGatePushDirectPaths(const uint64_t now) + { + if ((now - _lastDirectPathPushReceive) <= ZT_PUSH_DIRECT_PATHS_CUTOFF_TIME) + ++_directPathPushCutoffCount; + else _directPathPushCutoffCount = 0; + _lastDirectPathPushReceive = now; + return (_directPathPushCutoffCount < ZT_PUSH_DIRECT_PATHS_CUTOFF_LIMIT); + } + + /** + * Rate limit gate for VERB_NETWORK_CREDENTIALS + */ + inline bool rateGateCredentialsReceived(const uint64_t now) + { + if ((now - _lastCredentialsReceived) <= ZT_PEER_CREDENTIALS_CUTOFF_TIME) + ++_credentialsCutoffCount; + else _credentialsCutoffCount = 0; + _lastCredentialsReceived = now; + return (_directPathPushCutoffCount < ZT_PEER_CREDEITIALS_CUTOFF_LIMIT); + } + + /** + * Rate limit gate for sending of ERROR_NEED_MEMBERSHIP_CERTIFICATE + */ + inline bool rateGateRequestCredentials(const uint64_t now) + { + if ((now - _lastCredentialRequestSent) >= ZT_PEER_GENERAL_RATE_LIMIT) { + _lastCredentialRequestSent = now; + return true; + } + return false; + } + + /** + * Rate limit gate for inbound WHOIS requests + */ + inline bool rateGateInboundWhoisRequest(const uint64_t now) + { + if ((now - _lastWhoisRequestReceived) >= ZT_PEER_WHOIS_RATE_LIMIT) { + _lastWhoisRequestReceived = now; + return true; + } + return false; + } + + /** + * Rate limit gate for inbound ECHO requests + */ + inline bool rateGateEchoRequest(const uint64_t now) + { + if ((now - _lastEchoRequestReceived) >= ZT_PEER_GENERAL_RATE_LIMIT) { + _lastEchoRequestReceived = now; + return true; + } + return false; + } + + /** + * Rate gate incoming requests for network COM + */ + inline bool rateGateIncomingComRequest(const uint64_t now) + { + if ((now - _lastComRequestReceived) >= ZT_PEER_GENERAL_RATE_LIMIT) { + _lastComRequestReceived = now; + return true; + } + return false; + } + + /** + * Rate gate outgoing requests for network COM + */ + inline bool rateGateOutgoingComRequest(const uint64_t now) + { + if ((now - _lastComRequestSent) >= ZT_PEER_GENERAL_RATE_LIMIT) { + _lastComRequestSent = now; + return true; + } + return false; + } + +private: + struct _PeerPath + { +#ifdef ZT_ENABLE_CLUSTER + _PeerPath() : lr(0),p(),localClusterSuboptimal(false) {} +#else + _PeerPath() : lr(0),p() {} +#endif + uint64_t lr; // time of last valid ZeroTier packet + SharedPtr p; +#ifdef ZT_ENABLE_CLUSTER + bool localClusterSuboptimal; // true if our cluster has determined that we should not be serving this peer +#endif + }; + + uint8_t _key[ZT_PEER_SECRET_KEY_LENGTH]; + + const RuntimeEnvironment *RR; + + uint64_t _lastReceive; // direct or indirect + uint64_t _lastNontrivialReceive; // frames, things like netconf, etc. + uint64_t _lastTriedMemorizedPath; + uint64_t _lastDirectPathPushSent; + uint64_t _lastDirectPathPushReceive; + uint64_t _lastCredentialRequestSent; + uint64_t _lastWhoisRequestReceived; + uint64_t _lastEchoRequestReceived; + uint64_t _lastComRequestReceived; + uint64_t _lastComRequestSent; + uint64_t _lastCredentialsReceived; + uint64_t _lastTrustEstablishedPacketReceived; + + uint16_t _vProto; + uint16_t _vMajor; + uint16_t _vMinor; + uint16_t _vRevision; + + InetAddress _v4ClusterPreferred; + InetAddress _v6ClusterPreferred; + + _PeerPath _v4Path; // IPv4 direct path + _PeerPath _v6Path; // IPv6 direct path + Mutex _paths_m; + + Identity _id; + + unsigned int _latency; + unsigned int _directPathPushCutoffCount; + unsigned int _credentialsCutoffCount; + + AtomicCounter __refCount; +}; + +} // namespace ZeroTier + +// Add a swap() for shared ptr's to peers to speed up peer sorts +namespace std { + template<> + inline void swap(ZeroTier::SharedPtr &a,ZeroTier::SharedPtr &b) + { + a.swap(b); + } +} + +#endif diff --git a/node/Poly1305.cpp b/node/Poly1305.cpp new file mode 100644 index 0000000..13d4712 --- /dev/null +++ b/node/Poly1305.cpp @@ -0,0 +1,635 @@ +/* +20080912 +D. J. Bernstein +Public domain. +*/ + +#include "Constants.hpp" +#include "Poly1305.hpp" + +#include +#include +#include +#include + +#ifdef __WINDOWS__ +#pragma warning(disable: 4146) +#endif + +namespace ZeroTier { + +#if 0 + +// "Naive" implementation, which is slower... might still want this on some older +// or weird platforms if the later versions have issues. + +static inline void add(unsigned int h[17],const unsigned int c[17]) +{ + unsigned int j; + unsigned int u; + u = 0; + for (j = 0;j < 17;++j) { u += h[j] + c[j]; h[j] = u & 255; u >>= 8; } +} + +static inline void squeeze(unsigned int h[17]) +{ + unsigned int j; + unsigned int u; + u = 0; + for (j = 0;j < 16;++j) { u += h[j]; h[j] = u & 255; u >>= 8; } + u += h[16]; h[16] = u & 3; + u = 5 * (u >> 2); + for (j = 0;j < 16;++j) { u += h[j]; h[j] = u & 255; u >>= 8; } + u += h[16]; h[16] = u; +} + +static const unsigned int minusp[17] = { + 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252 +} ; + +static inline void freeze(unsigned int h[17]) +{ + unsigned int horig[17]; + unsigned int j; + unsigned int negative; + for (j = 0;j < 17;++j) horig[j] = h[j]; + add(h,minusp); + negative = -(h[16] >> 7); + for (j = 0;j < 17;++j) h[j] ^= negative & (horig[j] ^ h[j]); +} + +static inline void mulmod(unsigned int h[17],const unsigned int r[17]) +{ + unsigned int hr[17]; + unsigned int i; + unsigned int j; + unsigned int u; + + for (i = 0;i < 17;++i) { + u = 0; + for (j = 0;j <= i;++j) u += h[j] * r[i - j]; + for (j = i + 1;j < 17;++j) u += 320 * h[j] * r[i + 17 - j]; + hr[i] = u; + } + for (i = 0;i < 17;++i) h[i] = hr[i]; + squeeze(h); +} + +static inline int crypto_onetimeauth(unsigned char *out,const unsigned char *in,unsigned long long inlen,const unsigned char *k) +{ + unsigned int j; + unsigned int r[17]; + unsigned int h[17]; + unsigned int c[17]; + + r[0] = k[0]; + r[1] = k[1]; + r[2] = k[2]; + r[3] = k[3] & 15; + r[4] = k[4] & 252; + r[5] = k[5]; + r[6] = k[6]; + r[7] = k[7] & 15; + r[8] = k[8] & 252; + r[9] = k[9]; + r[10] = k[10]; + r[11] = k[11] & 15; + r[12] = k[12] & 252; + r[13] = k[13]; + r[14] = k[14]; + r[15] = k[15] & 15; + r[16] = 0; + + for (j = 0;j < 17;++j) h[j] = 0; + + while (inlen > 0) { + for (j = 0;j < 17;++j) c[j] = 0; + for (j = 0;(j < 16) && (j < inlen);++j) c[j] = in[j]; + c[j] = 1; + in += j; inlen -= j; + add(h,c); + mulmod(h,r); + } + + freeze(h); + + for (j = 0;j < 16;++j) c[j] = k[j + 16]; + c[16] = 0; + add(h,c); + for (j = 0;j < 16;++j) out[j] = h[j]; + return 0; +} + +void Poly1305::compute(void *auth,const void *data,unsigned int len,const void *key) + throw() +{ + crypto_onetimeauth((unsigned char *)auth,(const unsigned char *)data,len,(const unsigned char *)key); +} + +#endif + +namespace { + +typedef struct poly1305_context { + size_t aligner; + unsigned char opaque[136]; +} poly1305_context; + +#if (defined(_MSC_VER) || defined(__GNUC__)) && (defined(__amd64) || defined(__amd64__) || defined(__x86_64) || defined(__x86_64__) || defined(__AMD64) || defined(__AMD64__) || defined(_M_X64)) + +////////////////////////////////////////////////////////////////////////////// +// 128-bit implementation for MSC and GCC from Poly1305-donna + + +#if defined(_MSC_VER) + #include + + typedef struct uint128_t { + unsigned long long lo; + unsigned long long hi; + } uint128_t; + + #define MUL(out, x, y) out.lo = _umul128((x), (y), &out.hi) + #define ADD(out, in) { unsigned long long t = out.lo; out.lo += in.lo; out.hi += (out.lo < t) + in.hi; } + #define ADDLO(out, in) { unsigned long long t = out.lo; out.lo += in; out.hi += (out.lo < t); } + #define SHR(in, shift) (__shiftright128(in.lo, in.hi, (shift))) + #define LO(in) (in.lo) + +// #define POLY1305_NOINLINE __declspec(noinline) +#elif defined(__GNUC__) + #if defined(__SIZEOF_INT128__) + typedef unsigned __int128 uint128_t; + #else + typedef unsigned uint128_t __attribute__((mode(TI))); + #endif + + #define MUL(out, x, y) out = ((uint128_t)x * y) + #define ADD(out, in) out += in + #define ADDLO(out, in) out += in + #define SHR(in, shift) (unsigned long long)(in >> (shift)) + #define LO(in) (unsigned long long)(in) + +// #define POLY1305_NOINLINE __attribute__((noinline)) +#endif + +#define poly1305_block_size 16 + +/* 17 + sizeof(size_t) + 8*sizeof(unsigned long long) */ +typedef struct poly1305_state_internal_t { + unsigned long long r[3]; + unsigned long long h[3]; + unsigned long long pad[2]; + size_t leftover; + unsigned char buffer[poly1305_block_size]; + unsigned char final; +} poly1305_state_internal_t; + +#if defined(ZT_NO_TYPE_PUNNING) || (__BYTE_ORDER != __LITTLE_ENDIAN) +static inline unsigned long long U8TO64(const unsigned char *p) +{ + return + (((unsigned long long)(p[0] & 0xff) ) | + ((unsigned long long)(p[1] & 0xff) << 8) | + ((unsigned long long)(p[2] & 0xff) << 16) | + ((unsigned long long)(p[3] & 0xff) << 24) | + ((unsigned long long)(p[4] & 0xff) << 32) | + ((unsigned long long)(p[5] & 0xff) << 40) | + ((unsigned long long)(p[6] & 0xff) << 48) | + ((unsigned long long)(p[7] & 0xff) << 56)); +} +#else +#define U8TO64(p) (*reinterpret_cast(p)) +#endif + +#if defined(ZT_NO_TYPE_PUNNING) || (__BYTE_ORDER != __LITTLE_ENDIAN) +static inline void U64TO8(unsigned char *p, unsigned long long v) +{ + p[0] = (v ) & 0xff; + p[1] = (v >> 8) & 0xff; + p[2] = (v >> 16) & 0xff; + p[3] = (v >> 24) & 0xff; + p[4] = (v >> 32) & 0xff; + p[5] = (v >> 40) & 0xff; + p[6] = (v >> 48) & 0xff; + p[7] = (v >> 56) & 0xff; +} +#else +#define U64TO8(p,v) ((*reinterpret_cast(p)) = (v)) +#endif + +static inline void +poly1305_init(poly1305_context *ctx, const unsigned char key[32]) { + poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx; + unsigned long long t0,t1; + + /* r &= 0xffffffc0ffffffc0ffffffc0fffffff */ + t0 = U8TO64(&key[0]); + t1 = U8TO64(&key[8]); + + st->r[0] = ( t0 ) & 0xffc0fffffff; + st->r[1] = ((t0 >> 44) | (t1 << 20)) & 0xfffffc0ffff; + st->r[2] = ((t1 >> 24) ) & 0x00ffffffc0f; + + /* h = 0 */ + st->h[0] = 0; + st->h[1] = 0; + st->h[2] = 0; + + /* save pad for later */ + st->pad[0] = U8TO64(&key[16]); + st->pad[1] = U8TO64(&key[24]); + + st->leftover = 0; + st->final = 0; +} + +static inline void +poly1305_blocks(poly1305_state_internal_t *st, const unsigned char *m, size_t bytes) { + const unsigned long long hibit = (st->final) ? 0 : ((unsigned long long)1 << 40); /* 1 << 128 */ + unsigned long long r0,r1,r2; + unsigned long long s1,s2; + unsigned long long h0,h1,h2; + unsigned long long c; + uint128_t d0,d1,d2,d; + + r0 = st->r[0]; + r1 = st->r[1]; + r2 = st->r[2]; + + h0 = st->h[0]; + h1 = st->h[1]; + h2 = st->h[2]; + + s1 = r1 * (5 << 2); + s2 = r2 * (5 << 2); + + while (bytes >= poly1305_block_size) { + unsigned long long t0,t1; + + /* h += m[i] */ + t0 = U8TO64(&m[0]); + t1 = U8TO64(&m[8]); + + h0 += (( t0 ) & 0xfffffffffff); + h1 += (((t0 >> 44) | (t1 << 20)) & 0xfffffffffff); + h2 += (((t1 >> 24) ) & 0x3ffffffffff) | hibit; + + /* h *= r */ + MUL(d0, h0, r0); MUL(d, h1, s2); ADD(d0, d); MUL(d, h2, s1); ADD(d0, d); + MUL(d1, h0, r1); MUL(d, h1, r0); ADD(d1, d); MUL(d, h2, s2); ADD(d1, d); + MUL(d2, h0, r2); MUL(d, h1, r1); ADD(d2, d); MUL(d, h2, r0); ADD(d2, d); + + /* (partial) h %= p */ + c = SHR(d0, 44); h0 = LO(d0) & 0xfffffffffff; + ADDLO(d1, c); c = SHR(d1, 44); h1 = LO(d1) & 0xfffffffffff; + ADDLO(d2, c); c = SHR(d2, 42); h2 = LO(d2) & 0x3ffffffffff; + h0 += c * 5; c = (h0 >> 44); h0 = h0 & 0xfffffffffff; + h1 += c; + + m += poly1305_block_size; + bytes -= poly1305_block_size; + } + + st->h[0] = h0; + st->h[1] = h1; + st->h[2] = h2; +} + +static inline void +poly1305_finish(poly1305_context *ctx, unsigned char mac[16]) { + poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx; + unsigned long long h0,h1,h2,c; + unsigned long long g0,g1,g2; + unsigned long long t0,t1; + + /* process the remaining block */ + if (st->leftover) { + size_t i = st->leftover; + st->buffer[i] = 1; + for (i = i + 1; i < poly1305_block_size; i++) + st->buffer[i] = 0; + st->final = 1; + poly1305_blocks(st, st->buffer, poly1305_block_size); + } + + /* fully carry h */ + h0 = st->h[0]; + h1 = st->h[1]; + h2 = st->h[2]; + + c = (h1 >> 44); h1 &= 0xfffffffffff; + h2 += c; c = (h2 >> 42); h2 &= 0x3ffffffffff; + h0 += c * 5; c = (h0 >> 44); h0 &= 0xfffffffffff; + h1 += c; c = (h1 >> 44); h1 &= 0xfffffffffff; + h2 += c; c = (h2 >> 42); h2 &= 0x3ffffffffff; + h0 += c * 5; c = (h0 >> 44); h0 &= 0xfffffffffff; + h1 += c; + + /* compute h + -p */ + g0 = h0 + 5; c = (g0 >> 44); g0 &= 0xfffffffffff; + g1 = h1 + c; c = (g1 >> 44); g1 &= 0xfffffffffff; + g2 = h2 + c - ((unsigned long long)1 << 42); + + /* select h if h < p, or h + -p if h >= p */ + c = (g2 >> ((sizeof(unsigned long long) * 8) - 1)) - 1; + g0 &= c; + g1 &= c; + g2 &= c; + c = ~c; + h0 = (h0 & c) | g0; + h1 = (h1 & c) | g1; + h2 = (h2 & c) | g2; + + /* h = (h + pad) */ + t0 = st->pad[0]; + t1 = st->pad[1]; + + h0 += (( t0 ) & 0xfffffffffff) ; c = (h0 >> 44); h0 &= 0xfffffffffff; + h1 += (((t0 >> 44) | (t1 << 20)) & 0xfffffffffff) + c; c = (h1 >> 44); h1 &= 0xfffffffffff; + h2 += (((t1 >> 24) ) & 0x3ffffffffff) + c; h2 &= 0x3ffffffffff; + + /* mac = h % (2^128) */ + h0 = ((h0 ) | (h1 << 44)); + h1 = ((h1 >> 20) | (h2 << 24)); + + U64TO8(&mac[0], h0); + U64TO8(&mac[8], h1); + + /* zero out the state */ + st->h[0] = 0; + st->h[1] = 0; + st->h[2] = 0; + st->r[0] = 0; + st->r[1] = 0; + st->r[2] = 0; + st->pad[0] = 0; + st->pad[1] = 0; +} + +////////////////////////////////////////////////////////////////////////////// + +#else + +////////////////////////////////////////////////////////////////////////////// +// More portable 64-bit implementation + +#define poly1305_block_size 16 + +/* 17 + sizeof(size_t) + 14*sizeof(unsigned long) */ +typedef struct poly1305_state_internal_t { + unsigned long r[5]; + unsigned long h[5]; + unsigned long pad[4]; + size_t leftover; + unsigned char buffer[poly1305_block_size]; + unsigned char final; +} poly1305_state_internal_t; + +/* interpret four 8 bit unsigned integers as a 32 bit unsigned integer in little endian */ +static unsigned long +U8TO32(const unsigned char *p) { + return + (((unsigned long)(p[0] & 0xff) ) | + ((unsigned long)(p[1] & 0xff) << 8) | + ((unsigned long)(p[2] & 0xff) << 16) | + ((unsigned long)(p[3] & 0xff) << 24)); +} + +/* store a 32 bit unsigned integer as four 8 bit unsigned integers in little endian */ +static void +U32TO8(unsigned char *p, unsigned long v) { + p[0] = (v ) & 0xff; + p[1] = (v >> 8) & 0xff; + p[2] = (v >> 16) & 0xff; + p[3] = (v >> 24) & 0xff; +} + +static inline void +poly1305_init(poly1305_context *ctx, const unsigned char key[32]) { + poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx; + + /* r &= 0xffffffc0ffffffc0ffffffc0fffffff */ + st->r[0] = (U8TO32(&key[ 0]) ) & 0x3ffffff; + st->r[1] = (U8TO32(&key[ 3]) >> 2) & 0x3ffff03; + st->r[2] = (U8TO32(&key[ 6]) >> 4) & 0x3ffc0ff; + st->r[3] = (U8TO32(&key[ 9]) >> 6) & 0x3f03fff; + st->r[4] = (U8TO32(&key[12]) >> 8) & 0x00fffff; + + /* h = 0 */ + st->h[0] = 0; + st->h[1] = 0; + st->h[2] = 0; + st->h[3] = 0; + st->h[4] = 0; + + /* save pad for later */ + st->pad[0] = U8TO32(&key[16]); + st->pad[1] = U8TO32(&key[20]); + st->pad[2] = U8TO32(&key[24]); + st->pad[3] = U8TO32(&key[28]); + + st->leftover = 0; + st->final = 0; +} + +static inline void +poly1305_blocks(poly1305_state_internal_t *st, const unsigned char *m, size_t bytes) { + const unsigned long hibit = (st->final) ? 0 : (1 << 24); /* 1 << 128 */ + unsigned long r0,r1,r2,r3,r4; + unsigned long s1,s2,s3,s4; + unsigned long h0,h1,h2,h3,h4; + unsigned long long d0,d1,d2,d3,d4; + unsigned long c; + + r0 = st->r[0]; + r1 = st->r[1]; + r2 = st->r[2]; + r3 = st->r[3]; + r4 = st->r[4]; + + s1 = r1 * 5; + s2 = r2 * 5; + s3 = r3 * 5; + s4 = r4 * 5; + + h0 = st->h[0]; + h1 = st->h[1]; + h2 = st->h[2]; + h3 = st->h[3]; + h4 = st->h[4]; + + while (bytes >= poly1305_block_size) { + /* h += m[i] */ + h0 += (U8TO32(m+ 0) ) & 0x3ffffff; + h1 += (U8TO32(m+ 3) >> 2) & 0x3ffffff; + h2 += (U8TO32(m+ 6) >> 4) & 0x3ffffff; + h3 += (U8TO32(m+ 9) >> 6) & 0x3ffffff; + h4 += (U8TO32(m+12) >> 8) | hibit; + + /* h *= r */ + d0 = ((unsigned long long)h0 * r0) + ((unsigned long long)h1 * s4) + ((unsigned long long)h2 * s3) + ((unsigned long long)h3 * s2) + ((unsigned long long)h4 * s1); + d1 = ((unsigned long long)h0 * r1) + ((unsigned long long)h1 * r0) + ((unsigned long long)h2 * s4) + ((unsigned long long)h3 * s3) + ((unsigned long long)h4 * s2); + d2 = ((unsigned long long)h0 * r2) + ((unsigned long long)h1 * r1) + ((unsigned long long)h2 * r0) + ((unsigned long long)h3 * s4) + ((unsigned long long)h4 * s3); + d3 = ((unsigned long long)h0 * r3) + ((unsigned long long)h1 * r2) + ((unsigned long long)h2 * r1) + ((unsigned long long)h3 * r0) + ((unsigned long long)h4 * s4); + d4 = ((unsigned long long)h0 * r4) + ((unsigned long long)h1 * r3) + ((unsigned long long)h2 * r2) + ((unsigned long long)h3 * r1) + ((unsigned long long)h4 * r0); + + /* (partial) h %= p */ + c = (unsigned long)(d0 >> 26); h0 = (unsigned long)d0 & 0x3ffffff; + d1 += c; c = (unsigned long)(d1 >> 26); h1 = (unsigned long)d1 & 0x3ffffff; + d2 += c; c = (unsigned long)(d2 >> 26); h2 = (unsigned long)d2 & 0x3ffffff; + d3 += c; c = (unsigned long)(d3 >> 26); h3 = (unsigned long)d3 & 0x3ffffff; + d4 += c; c = (unsigned long)(d4 >> 26); h4 = (unsigned long)d4 & 0x3ffffff; + h0 += c * 5; c = (h0 >> 26); h0 = h0 & 0x3ffffff; + h1 += c; + + m += poly1305_block_size; + bytes -= poly1305_block_size; + } + + st->h[0] = h0; + st->h[1] = h1; + st->h[2] = h2; + st->h[3] = h3; + st->h[4] = h4; +} + +static inline void +poly1305_finish(poly1305_context *ctx, unsigned char mac[16]) { + poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx; + unsigned long h0,h1,h2,h3,h4,c; + unsigned long g0,g1,g2,g3,g4; + unsigned long long f; + unsigned long mask; + + /* process the remaining block */ + if (st->leftover) { + size_t i = st->leftover; + st->buffer[i++] = 1; + for (; i < poly1305_block_size; i++) + st->buffer[i] = 0; + st->final = 1; + poly1305_blocks(st, st->buffer, poly1305_block_size); + } + + /* fully carry h */ + h0 = st->h[0]; + h1 = st->h[1]; + h2 = st->h[2]; + h3 = st->h[3]; + h4 = st->h[4]; + + c = h1 >> 26; h1 = h1 & 0x3ffffff; + h2 += c; c = h2 >> 26; h2 = h2 & 0x3ffffff; + h3 += c; c = h3 >> 26; h3 = h3 & 0x3ffffff; + h4 += c; c = h4 >> 26; h4 = h4 & 0x3ffffff; + h0 += c * 5; c = h0 >> 26; h0 = h0 & 0x3ffffff; + h1 += c; + + /* compute h + -p */ + g0 = h0 + 5; c = g0 >> 26; g0 &= 0x3ffffff; + g1 = h1 + c; c = g1 >> 26; g1 &= 0x3ffffff; + g2 = h2 + c; c = g2 >> 26; g2 &= 0x3ffffff; + g3 = h3 + c; c = g3 >> 26; g3 &= 0x3ffffff; + g4 = h4 + c - (1 << 26); + + /* select h if h < p, or h + -p if h >= p */ + mask = (g4 >> ((sizeof(unsigned long) * 8) - 1)) - 1; + g0 &= mask; + g1 &= mask; + g2 &= mask; + g3 &= mask; + g4 &= mask; + mask = ~mask; + h0 = (h0 & mask) | g0; + h1 = (h1 & mask) | g1; + h2 = (h2 & mask) | g2; + h3 = (h3 & mask) | g3; + h4 = (h4 & mask) | g4; + + /* h = h % (2^128) */ + h0 = ((h0 ) | (h1 << 26)) & 0xffffffff; + h1 = ((h1 >> 6) | (h2 << 20)) & 0xffffffff; + h2 = ((h2 >> 12) | (h3 << 14)) & 0xffffffff; + h3 = ((h3 >> 18) | (h4 << 8)) & 0xffffffff; + + /* mac = (h + pad) % (2^128) */ + f = (unsigned long long)h0 + st->pad[0] ; h0 = (unsigned long)f; + f = (unsigned long long)h1 + st->pad[1] + (f >> 32); h1 = (unsigned long)f; + f = (unsigned long long)h2 + st->pad[2] + (f >> 32); h2 = (unsigned long)f; + f = (unsigned long long)h3 + st->pad[3] + (f >> 32); h3 = (unsigned long)f; + + U32TO8(mac + 0, h0); + U32TO8(mac + 4, h1); + U32TO8(mac + 8, h2); + U32TO8(mac + 12, h3); + + /* zero out the state */ + st->h[0] = 0; + st->h[1] = 0; + st->h[2] = 0; + st->h[3] = 0; + st->h[4] = 0; + st->r[0] = 0; + st->r[1] = 0; + st->r[2] = 0; + st->r[3] = 0; + st->r[4] = 0; + st->pad[0] = 0; + st->pad[1] = 0; + st->pad[2] = 0; + st->pad[3] = 0; +} + +////////////////////////////////////////////////////////////////////////////// + +#endif // MSC/GCC or not + +static inline void +poly1305_update(poly1305_context *ctx, const unsigned char *m, size_t bytes) { + poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx; + size_t i; + + /* handle leftover */ + if (st->leftover) { + size_t want = (poly1305_block_size - st->leftover); + if (want > bytes) + want = bytes; + for (i = 0; i < want; i++) + st->buffer[st->leftover + i] = m[i]; + bytes -= want; + m += want; + st->leftover += want; + if (st->leftover < poly1305_block_size) + return; + poly1305_blocks(st, st->buffer, poly1305_block_size); + st->leftover = 0; + } + + /* process full blocks */ + if (bytes >= poly1305_block_size) { + size_t want = (bytes & ~(poly1305_block_size - 1)); + poly1305_blocks(st, m, want); + m += want; + bytes -= want; + } + + /* store leftover */ + if (bytes) { + for (i = 0; i < bytes; i++) + st->buffer[st->leftover + i] = m[i]; + st->leftover += bytes; + } +} + +} // anonymous namespace + +void Poly1305::compute(void *auth,const void *data,unsigned int len,const void *key) + throw() +{ + poly1305_context ctx; + poly1305_init(&ctx,reinterpret_cast(key)); + poly1305_update(&ctx,reinterpret_cast(data),(size_t)len); + poly1305_finish(&ctx,reinterpret_cast(auth)); +} + +} // namespace ZeroTier diff --git a/node/Poly1305.hpp b/node/Poly1305.hpp new file mode 100644 index 0000000..62d5754 --- /dev/null +++ b/node/Poly1305.hpp @@ -0,0 +1,55 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_POLY1305_HPP +#define ZT_POLY1305_HPP + +namespace ZeroTier { + +#define ZT_POLY1305_KEY_LEN 32 +#define ZT_POLY1305_MAC_LEN 16 + +/** + * Poly1305 one-time authentication code + * + * This takes a one-time-use 32-byte key and generates a 16-byte message + * authentication code. The key must never be re-used for a different + * message. + * + * In Packet this is done by using the first 32 bytes of the stream cipher + * keystream as a one-time-use key. These 32 bytes are then discarded and + * the packet is encrypted with the next N bytes. + */ +class Poly1305 +{ +public: + /** + * Compute a one-time authentication code + * + * @param auth Buffer to receive code -- MUST be 16 bytes in length + * @param data Data to authenticate + * @param len Length of data to authenticate in bytes + * @param key 32-byte one-time use key to authenticate data (must not be reused) + */ + static void compute(void *auth,const void *data,unsigned int len,const void *key) + throw(); +}; + +} // namespace ZeroTier + +#endif diff --git a/node/README.md b/node/README.md new file mode 100644 index 0000000..1728400 --- /dev/null +++ b/node/README.md @@ -0,0 +1,14 @@ +ZeroTier Network Hypervisor Core +====== + +This directory contains the *real* ZeroTier: a completely OS-independent global virtual Ethernet switch engine. This is where the magic happens. + +Give it wire packets and it gives you Ethernet packets, and vice versa. The core contains absolutely no actual I/O, port configuration, or other OS-specific code (except Utils::getSecureRandom()). It provides a simple C API via [/include/ZeroTierOne.h](../include/ZeroTierOne.h). It's designed to be small and maximally portable for future use on small embedded and special purpose systems. + +Code in here follows these guidelines: + + - Keep it minimal, especially in terms of code footprint and memory use. + - There should be no OS-dependent code here unless absolutely necessary (e.g. getSecureRandom). + - If it's not part of the core virtual Ethernet switch it does not belong here. + - No C++11 or C++14 since older and embedded compilers don't support it yet and this should be maximally portable. + - Minimize the use of complex C++ features since at some point we might end up "minus-minus'ing" this code if doing so proves necessary to port to tiny embedded systems. diff --git a/node/Revocation.cpp b/node/Revocation.cpp new file mode 100644 index 0000000..bab5653 --- /dev/null +++ b/node/Revocation.cpp @@ -0,0 +1,46 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include "Revocation.hpp" +#include "RuntimeEnvironment.hpp" +#include "Identity.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Network.hpp" + +namespace ZeroTier { + +int Revocation::verify(const RuntimeEnvironment *RR,void *tPtr) const +{ + if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) + return -1; + const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); + if (!id) { + RR->sw->requestWhois(tPtr,_signedBy); + return 1; + } + try { + Buffer tmp; + this->serialize(tmp,true); + return (id.verify(tmp.data(),tmp.size(),_signature) ? 0 : -1); + } catch ( ... ) { + return -1; + } +} + +} // namespace ZeroTier diff --git a/node/Revocation.hpp b/node/Revocation.hpp new file mode 100644 index 0000000..e5e013b --- /dev/null +++ b/node/Revocation.hpp @@ -0,0 +1,189 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_REVOCATION_HPP +#define ZT_REVOCATION_HPP + +#include +#include +#include +#include + +#include "Constants.hpp" +#include "../include/ZeroTierOne.h" +#include "Credential.hpp" +#include "Address.hpp" +#include "C25519.hpp" +#include "Utils.hpp" +#include "Buffer.hpp" +#include "Identity.hpp" + +/** + * Flag: fast propagation via rumor mill algorithm + */ +#define ZT_REVOCATION_FLAG_FAST_PROPAGATE 0x1ULL + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Revocation certificate to instantaneously revoke a COM, capability, or tag + */ +class Revocation : public Credential +{ +public: + static inline Credential::Type credentialType() { return Credential::CREDENTIAL_TYPE_REVOCATION; } + + Revocation() + { + memset(this,0,sizeof(Revocation)); + } + + /** + * @param i ID (arbitrary for revocations, currently random) + * @param nwid Network ID + * @param cid Credential ID being revoked (0 for all or for COMs, which lack IDs) + * @param thr Revocation time threshold before which credentials will be revoked + * @param fl Flags + * @param tgt Target node whose credential(s) are being revoked + * @param ct Credential type being revoked + */ + Revocation(const uint32_t i,const uint64_t nwid,const uint32_t cid,const uint64_t thr,const uint64_t fl,const Address &tgt,const Credential::Type ct) : + _id(i), + _credentialId(cid), + _networkId(nwid), + _threshold(thr), + _flags(fl), + _target(tgt), + _signedBy(), + _type(ct) {} + + inline uint32_t id() const { return _id; } + inline uint32_t credentialId() const { return _credentialId; } + inline uint64_t networkId() const { return _networkId; } + inline uint64_t threshold() const { return _threshold; } + inline const Address &target() const { return _target; } + inline const Address &signer() const { return _signedBy; } + inline Credential::Type type() const { return _type; } + + inline bool fastPropagate() const { return ((_flags & ZT_REVOCATION_FLAG_FAST_PROPAGATE) != 0); } + + /** + * @param signer Signing identity, must have private key + * @return True if signature was successful + */ + inline bool sign(const Identity &signer) + { + if (signer.hasPrivate()) { + Buffer tmp; + _signedBy = signer.address(); + this->serialize(tmp,true); + _signature = signer.sign(tmp.data(),tmp.size()); + return true; + } + return false; + } + + /** + * Verify this revocation's signature + * + * @param RR Runtime environment to provide for peer lookup, etc. + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or chain + */ + int verify(const RuntimeEnvironment *RR,void *tPtr) const; + + template + inline void serialize(Buffer &b,const bool forSign = false) const + { + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + + b.append((uint32_t)0); // 4 unused bytes, currently set to 0 + b.append(_id); + b.append(_networkId); + b.append((uint32_t)0); // 4 unused bytes, currently set to 0 + b.append(_credentialId); + b.append(_threshold); + b.append(_flags); + _target.appendTo(b); + _signedBy.appendTo(b); + b.append((uint8_t)_type); + + if (!forSign) { + b.append((uint8_t)1); // 1 == Ed25519 signature + b.append((uint16_t)ZT_C25519_SIGNATURE_LEN); + b.append(_signature.data,ZT_C25519_SIGNATURE_LEN); + } + + // This is the size of any additional fields, currently 0. + b.append((uint16_t)0); + + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } + + template + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + memset(this,0,sizeof(Revocation)); + + unsigned int p = startAt; + + p += 4; // 4 bytes, currently unused + _id = b.template at(p); p += 4; + _networkId = b.template at(p); p += 8; + p += 4; // 4 bytes, currently unused + _credentialId = b.template at(p); p += 4; + _threshold = b.template at(p); p += 8; + _flags = b.template at(p); p += 8; + _target.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + _type = (Credential::Type)b[p++]; + + if (b[p++] == 1) { + if (b.template at(p) == ZT_C25519_SIGNATURE_LEN) { + p += 2; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); + p += ZT_C25519_SIGNATURE_LEN; + } else throw std::runtime_error("invalid signature"); + } else { + p += 2 + b.template at(p); + } + + p += 2 + b.template at(p); + if (p > b.size()) + throw std::runtime_error("extended field overflow"); + + return (p - startAt); + } + +private: + uint32_t _id; + uint32_t _credentialId; + uint64_t _networkId; + uint64_t _threshold; + uint64_t _flags; + Address _target; + Address _signedBy; + Credential::Type _type; + C25519::Signature _signature; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/RuntimeEnvironment.hpp b/node/RuntimeEnvironment.hpp new file mode 100644 index 0000000..7ba1c98 --- /dev/null +++ b/node/RuntimeEnvironment.hpp @@ -0,0 +1,89 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_RUNTIMEENVIRONMENT_HPP +#define ZT_RUNTIMEENVIRONMENT_HPP + +#include + +#include "Constants.hpp" +#include "Identity.hpp" +#include "Mutex.hpp" + +namespace ZeroTier { + +class NodeConfig; +class Switch; +class Topology; +class Node; +class Multicaster; +class NetworkController; +class SelfAwareness; +class Cluster; + +/** + * Holds global state for an instance of ZeroTier::Node + */ +class RuntimeEnvironment +{ +public: + RuntimeEnvironment(Node *n) : + node(n) + ,identity() + ,localNetworkController((NetworkController *)0) + ,sw((Switch *)0) + ,mc((Multicaster *)0) + ,topology((Topology *)0) + ,sa((SelfAwareness *)0) +#ifdef ZT_ENABLE_CLUSTER + ,cluster((Cluster *)0) +#endif + { + } + + // Node instance that owns this RuntimeEnvironment + Node *const node; + + // This node's identity + Identity identity; + std::string publicIdentityStr; + std::string secretIdentityStr; + + // This is set externally to an instance of this base class + NetworkController *localNetworkController; + + /* + * Order matters a bit here. These are constructed in this order + * and then deleted in the opposite order on Node exit. The order ensures + * that things that are needed are there before they're needed. + * + * These are constant and never null after startup unless indicated. + */ + + Switch *sw; + Multicaster *mc; + Topology *topology; + SelfAwareness *sa; +#ifdef ZT_ENABLE_CLUSTER + Cluster *cluster; +#endif +}; + +} // namespace ZeroTier + +#endif diff --git a/node/SHA512.cpp b/node/SHA512.cpp new file mode 100644 index 0000000..76737d3 --- /dev/null +++ b/node/SHA512.cpp @@ -0,0 +1,352 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include + +#include "SHA512.hpp" +#include "Utils.hpp" + +namespace ZeroTier { + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +// Code taken from NaCl by D. J. Bernstein and others +// Public domain + +/* +20080913 +D. J. Bernstein +Public domain. +*/ + +#define uint64 uint64_t + +#ifdef ZT_NO_TYPE_PUNNING + +static uint64 load_bigendian(const unsigned char *x) +{ + return + (uint64) (x[7]) \ + | (((uint64) (x[6])) << 8) \ + | (((uint64) (x[5])) << 16) \ + | (((uint64) (x[4])) << 24) \ + | (((uint64) (x[3])) << 32) \ + | (((uint64) (x[2])) << 40) \ + | (((uint64) (x[1])) << 48) \ + | (((uint64) (x[0])) << 56) + ; +} + +static void store_bigendian(unsigned char *x,uint64 u) +{ + x[7] = u; u >>= 8; + x[6] = u; u >>= 8; + x[5] = u; u >>= 8; + x[4] = u; u >>= 8; + x[3] = u; u >>= 8; + x[2] = u; u >>= 8; + x[1] = u; u >>= 8; + x[0] = u; +} + +#else // !ZT_NO_TYPE_PUNNING + +#define load_bigendian(x) Utils::ntoh(*((const uint64_t *)(x))) +#define store_bigendian(x,u) (*((uint64_t *)(x)) = Utils::hton((u))) + +#endif // ZT_NO_TYPE_PUNNING + +#define SHR(x,c) ((x) >> (c)) +#define ROTR(x,c) (((x) >> (c)) | ((x) << (64 - (c)))) + +#define Ch(x,y,z) ((x & y) ^ (~x & z)) +#define Maj(x,y,z) ((x & y) ^ (x & z) ^ (y & z)) +#define Sigma0(x) (ROTR(x,28) ^ ROTR(x,34) ^ ROTR(x,39)) +#define Sigma1(x) (ROTR(x,14) ^ ROTR(x,18) ^ ROTR(x,41)) +#define sigma0(x) (ROTR(x, 1) ^ ROTR(x, 8) ^ SHR(x,7)) +#define sigma1(x) (ROTR(x,19) ^ ROTR(x,61) ^ SHR(x,6)) + +#define M(w0,w14,w9,w1) w0 = sigma1(w14) + w9 + sigma0(w1) + w0; + +#define EXPAND \ + M(w0 ,w14,w9 ,w1 ) \ + M(w1 ,w15,w10,w2 ) \ + M(w2 ,w0 ,w11,w3 ) \ + M(w3 ,w1 ,w12,w4 ) \ + M(w4 ,w2 ,w13,w5 ) \ + M(w5 ,w3 ,w14,w6 ) \ + M(w6 ,w4 ,w15,w7 ) \ + M(w7 ,w5 ,w0 ,w8 ) \ + M(w8 ,w6 ,w1 ,w9 ) \ + M(w9 ,w7 ,w2 ,w10) \ + M(w10,w8 ,w3 ,w11) \ + M(w11,w9 ,w4 ,w12) \ + M(w12,w10,w5 ,w13) \ + M(w13,w11,w6 ,w14) \ + M(w14,w12,w7 ,w15) \ + M(w15,w13,w8 ,w0 ) + +#define F(w,k) \ + T1 = h + Sigma1(e) + Ch(e,f,g) + k + w; \ + T2 = Sigma0(a) + Maj(a,b,c); \ + h = g; \ + g = f; \ + f = e; \ + e = d + T1; \ + d = c; \ + c = b; \ + b = a; \ + a = T1 + T2; + +static inline int crypto_hashblocks(unsigned char *statebytes,const unsigned char *in,unsigned long long inlen) +{ + uint64 state[8]; + uint64 a; + uint64 b; + uint64 c; + uint64 d; + uint64 e; + uint64 f; + uint64 g; + uint64 h; + uint64 T1; + uint64 T2; + + a = load_bigendian(statebytes + 0); state[0] = a; + b = load_bigendian(statebytes + 8); state[1] = b; + c = load_bigendian(statebytes + 16); state[2] = c; + d = load_bigendian(statebytes + 24); state[3] = d; + e = load_bigendian(statebytes + 32); state[4] = e; + f = load_bigendian(statebytes + 40); state[5] = f; + g = load_bigendian(statebytes + 48); state[6] = g; + h = load_bigendian(statebytes + 56); state[7] = h; + + while (inlen >= 128) { + uint64 w0 = load_bigendian(in + 0); + uint64 w1 = load_bigendian(in + 8); + uint64 w2 = load_bigendian(in + 16); + uint64 w3 = load_bigendian(in + 24); + uint64 w4 = load_bigendian(in + 32); + uint64 w5 = load_bigendian(in + 40); + uint64 w6 = load_bigendian(in + 48); + uint64 w7 = load_bigendian(in + 56); + uint64 w8 = load_bigendian(in + 64); + uint64 w9 = load_bigendian(in + 72); + uint64 w10 = load_bigendian(in + 80); + uint64 w11 = load_bigendian(in + 88); + uint64 w12 = load_bigendian(in + 96); + uint64 w13 = load_bigendian(in + 104); + uint64 w14 = load_bigendian(in + 112); + uint64 w15 = load_bigendian(in + 120); + + F(w0 ,0x428a2f98d728ae22ULL) + F(w1 ,0x7137449123ef65cdULL) + F(w2 ,0xb5c0fbcfec4d3b2fULL) + F(w3 ,0xe9b5dba58189dbbcULL) + F(w4 ,0x3956c25bf348b538ULL) + F(w5 ,0x59f111f1b605d019ULL) + F(w6 ,0x923f82a4af194f9bULL) + F(w7 ,0xab1c5ed5da6d8118ULL) + F(w8 ,0xd807aa98a3030242ULL) + F(w9 ,0x12835b0145706fbeULL) + F(w10,0x243185be4ee4b28cULL) + F(w11,0x550c7dc3d5ffb4e2ULL) + F(w12,0x72be5d74f27b896fULL) + F(w13,0x80deb1fe3b1696b1ULL) + F(w14,0x9bdc06a725c71235ULL) + F(w15,0xc19bf174cf692694ULL) + + EXPAND + + F(w0 ,0xe49b69c19ef14ad2ULL) + F(w1 ,0xefbe4786384f25e3ULL) + F(w2 ,0x0fc19dc68b8cd5b5ULL) + F(w3 ,0x240ca1cc77ac9c65ULL) + F(w4 ,0x2de92c6f592b0275ULL) + F(w5 ,0x4a7484aa6ea6e483ULL) + F(w6 ,0x5cb0a9dcbd41fbd4ULL) + F(w7 ,0x76f988da831153b5ULL) + F(w8 ,0x983e5152ee66dfabULL) + F(w9 ,0xa831c66d2db43210ULL) + F(w10,0xb00327c898fb213fULL) + F(w11,0xbf597fc7beef0ee4ULL) + F(w12,0xc6e00bf33da88fc2ULL) + F(w13,0xd5a79147930aa725ULL) + F(w14,0x06ca6351e003826fULL) + F(w15,0x142929670a0e6e70ULL) + + EXPAND + + F(w0 ,0x27b70a8546d22ffcULL) + F(w1 ,0x2e1b21385c26c926ULL) + F(w2 ,0x4d2c6dfc5ac42aedULL) + F(w3 ,0x53380d139d95b3dfULL) + F(w4 ,0x650a73548baf63deULL) + F(w5 ,0x766a0abb3c77b2a8ULL) + F(w6 ,0x81c2c92e47edaee6ULL) + F(w7 ,0x92722c851482353bULL) + F(w8 ,0xa2bfe8a14cf10364ULL) + F(w9 ,0xa81a664bbc423001ULL) + F(w10,0xc24b8b70d0f89791ULL) + F(w11,0xc76c51a30654be30ULL) + F(w12,0xd192e819d6ef5218ULL) + F(w13,0xd69906245565a910ULL) + F(w14,0xf40e35855771202aULL) + F(w15,0x106aa07032bbd1b8ULL) + + EXPAND + + F(w0 ,0x19a4c116b8d2d0c8ULL) + F(w1 ,0x1e376c085141ab53ULL) + F(w2 ,0x2748774cdf8eeb99ULL) + F(w3 ,0x34b0bcb5e19b48a8ULL) + F(w4 ,0x391c0cb3c5c95a63ULL) + F(w5 ,0x4ed8aa4ae3418acbULL) + F(w6 ,0x5b9cca4f7763e373ULL) + F(w7 ,0x682e6ff3d6b2b8a3ULL) + F(w8 ,0x748f82ee5defb2fcULL) + F(w9 ,0x78a5636f43172f60ULL) + F(w10,0x84c87814a1f0ab72ULL) + F(w11,0x8cc702081a6439ecULL) + F(w12,0x90befffa23631e28ULL) + F(w13,0xa4506cebde82bde9ULL) + F(w14,0xbef9a3f7b2c67915ULL) + F(w15,0xc67178f2e372532bULL) + + EXPAND + + F(w0 ,0xca273eceea26619cULL) + F(w1 ,0xd186b8c721c0c207ULL) + F(w2 ,0xeada7dd6cde0eb1eULL) + F(w3 ,0xf57d4f7fee6ed178ULL) + F(w4 ,0x06f067aa72176fbaULL) + F(w5 ,0x0a637dc5a2c898a6ULL) + F(w6 ,0x113f9804bef90daeULL) + F(w7 ,0x1b710b35131c471bULL) + F(w8 ,0x28db77f523047d84ULL) + F(w9 ,0x32caab7b40c72493ULL) + F(w10,0x3c9ebe0a15c9bebcULL) + F(w11,0x431d67c49c100d4cULL) + F(w12,0x4cc5d4becb3e42b6ULL) + F(w13,0x597f299cfc657e2aULL) + F(w14,0x5fcb6fab3ad6faecULL) + F(w15,0x6c44198c4a475817ULL) + + a += state[0]; + b += state[1]; + c += state[2]; + d += state[3]; + e += state[4]; + f += state[5]; + g += state[6]; + h += state[7]; + + state[0] = a; + state[1] = b; + state[2] = c; + state[3] = d; + state[4] = e; + state[5] = f; + state[6] = g; + state[7] = h; + + in += 128; + inlen -= 128; + } + + store_bigendian(statebytes + 0,state[0]); + store_bigendian(statebytes + 8,state[1]); + store_bigendian(statebytes + 16,state[2]); + store_bigendian(statebytes + 24,state[3]); + store_bigendian(statebytes + 32,state[4]); + store_bigendian(statebytes + 40,state[5]); + store_bigendian(statebytes + 48,state[6]); + store_bigendian(statebytes + 56,state[7]); + + return 0; +} + +#define blocks crypto_hashblocks + +static const unsigned char iv[64] = { + 0x6a,0x09,0xe6,0x67,0xf3,0xbc,0xc9,0x08, + 0xbb,0x67,0xae,0x85,0x84,0xca,0xa7,0x3b, + 0x3c,0x6e,0xf3,0x72,0xfe,0x94,0xf8,0x2b, + 0xa5,0x4f,0xf5,0x3a,0x5f,0x1d,0x36,0xf1, + 0x51,0x0e,0x52,0x7f,0xad,0xe6,0x82,0xd1, + 0x9b,0x05,0x68,0x8c,0x2b,0x3e,0x6c,0x1f, + 0x1f,0x83,0xd9,0xab,0xfb,0x41,0xbd,0x6b, + 0x5b,0xe0,0xcd,0x19,0x13,0x7e,0x21,0x79 +}; + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +void SHA512::hash(void *digest,const void *data,unsigned int len) +{ + unsigned char h[64]; + unsigned char padded[256]; + int i; + uint64_t bytes = len; + + const unsigned char *in = (const unsigned char *)data; + unsigned int inlen = len; + + for (i = 0;i < 64;++i) h[i] = iv[i]; + + blocks(h,in,inlen); + in += inlen; + inlen &= 127; + in -= inlen; + + for (i = 0;i < (int)inlen;++i) padded[i] = in[i]; + padded[inlen] = 0x80; + + if (inlen < 112) { + for (i = inlen + 1;i < 119;++i) padded[i] = 0; + padded[119] = (unsigned char)((bytes >> 61) & 0xff); + padded[120] = (unsigned char)((bytes >> 53) & 0xff); + padded[121] = (unsigned char)((bytes >> 45) & 0xff); + padded[122] = (unsigned char)((bytes >> 37) & 0xff); + padded[123] = (unsigned char)((bytes >> 29) & 0xff); + padded[124] = (unsigned char)((bytes >> 21) & 0xff); + padded[125] = (unsigned char)((bytes >> 13) & 0xff); + padded[126] = (unsigned char)((bytes >> 5) & 0xff); + padded[127] = (unsigned char)((bytes << 3) & 0xff); + blocks(h,padded,128); + } else { + for (i = inlen + 1;i < 247;++i) padded[i] = 0; + padded[247] = (unsigned char)((bytes >> 61) & 0xff); + padded[248] = (unsigned char)((bytes >> 53) & 0xff); + padded[249] = (unsigned char)((bytes >> 45) & 0xff); + padded[250] = (unsigned char)((bytes >> 37) & 0xff); + padded[251] = (unsigned char)((bytes >> 29) & 0xff); + padded[252] = (unsigned char)((bytes >> 21) & 0xff); + padded[253] = (unsigned char)((bytes >> 13) & 0xff); + padded[254] = (unsigned char)((bytes >> 5) & 0xff); + padded[255] = (unsigned char)((bytes << 3) & 0xff); + blocks(h,padded,256); + } + + for (i = 0;i < 64;++i) ((unsigned char *)digest)[i] = h[i]; +} + +} // namespace ZeroTier diff --git a/node/SHA512.hpp b/node/SHA512.hpp new file mode 100644 index 0000000..639a7df --- /dev/null +++ b/node/SHA512.hpp @@ -0,0 +1,37 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_SHA512_HPP +#define ZT_SHA512_HPP + +#define ZT_SHA512_DIGEST_LEN 64 + +namespace ZeroTier { + +/** + * SHA-512 digest algorithm + */ +class SHA512 +{ +public: + static void hash(void *digest,const void *data,unsigned int len); +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Salsa20.cpp b/node/Salsa20.cpp new file mode 100644 index 0000000..1d4117e --- /dev/null +++ b/node/Salsa20.cpp @@ -0,0 +1,1341 @@ +/* + * Based on public domain code available at: http://cr.yp.to/snuffle.html + * + * Modifications and C-native SSE macro based SSE implementation by + * Adam Ierymenko . + * + * Since the original was public domain, this is too. + */ + +#include "Constants.hpp" +#include "Salsa20.hpp" + +#define ROTATE(v,c) (((v) << (c)) | ((v) >> (32 - (c)))) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) ((uint32_t)((v) + (w))) + +// Set up laod/store macros with appropriate endianness (we don't use these in SSE mode) +#ifndef ZT_SALSA20_SSE + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +#ifdef ZT_NO_TYPE_PUNNING +// Slower version that does not use type punning +#define U8TO32_LITTLE(p) ( ((uint32_t)(p)[0]) | ((uint32_t)(p)[1] << 8) | ((uint32_t)(p)[2] << 16) | ((uint32_t)(p)[3] << 24) ) +static inline void U32TO8_LITTLE(uint8_t *const c,const uint32_t v) { c[0] = (uint8_t)v; c[1] = (uint8_t)(v >> 8); c[2] = (uint8_t)(v >> 16); c[3] = (uint8_t)(v >> 24); } +#else +// Fast version that just does 32-bit load/store +#define U8TO32_LITTLE(p) (*((const uint32_t *)((const void *)(p)))) +#define U32TO8_LITTLE(c,v) *((uint32_t *)((void *)(c))) = (v) +#endif // ZT_NO_TYPE_PUNNING + +#else // __BYTE_ORDER == __BIG_ENDIAN (we don't support anything else... does MIDDLE_ENDIAN even still exist?) + +#ifdef __GNUC__ + +// Use GNUC builtin bswap macros on big-endian machines if available +#define U8TO32_LITTLE(p) __builtin_bswap32(*((const uint32_t *)((const void *)(p)))) +#define U32TO8_LITTLE(c,v) *((uint32_t *)((void *)(c))) = __builtin_bswap32((v)) + +#else // no __GNUC__ + +// Otherwise do it the slow, manual way on BE machines +#define U8TO32_LITTLE(p) ( ((uint32_t)(p)[0]) | ((uint32_t)(p)[1] << 8) | ((uint32_t)(p)[2] << 16) | ((uint32_t)(p)[3] << 24) ) +static inline void U32TO8_LITTLE(uint8_t *const c,const uint32_t v) { c[0] = (uint8_t)v; c[1] = (uint8_t)(v >> 8); c[2] = (uint8_t)(v >> 16); c[3] = (uint8_t)(v >> 24); } + +#endif // __GNUC__ or not + +#endif // __BYTE_ORDER little or big? + +#endif // !ZT_SALSA20_SSE + +// Statically compute and define SSE constants +#ifdef ZT_SALSA20_SSE +class _s20sseconsts +{ +public: + _s20sseconsts() + { + maskLo32 = _mm_shuffle_epi32(_mm_cvtsi32_si128(-1), _MM_SHUFFLE(1, 0, 1, 0)); + maskHi32 = _mm_slli_epi64(maskLo32, 32); + } + __m128i maskLo32,maskHi32; +}; +static const _s20sseconsts _S20SSECONSTANTS; +#endif + +namespace ZeroTier { + +void Salsa20::init(const void *key,const void *iv) +{ +#ifdef ZT_SALSA20_SSE + const uint32_t *const k = (const uint32_t *)key; + _state.i[0] = 0x61707865; + _state.i[1] = 0x3320646e; + _state.i[2] = 0x79622d32; + _state.i[3] = 0x6b206574; + _state.i[4] = k[3]; + _state.i[5] = 0; + _state.i[6] = k[7]; + _state.i[7] = k[2]; + _state.i[8] = 0; + _state.i[9] = k[6]; + _state.i[10] = k[1]; + _state.i[11] = ((const uint32_t *)iv)[1]; + _state.i[12] = k[5]; + _state.i[13] = k[0]; + _state.i[14] = ((const uint32_t *)iv)[0]; + _state.i[15] = k[4]; +#else + const char *const constants = "expand 32-byte k"; + const uint8_t *const k = (const uint8_t *)key; + _state.i[0] = U8TO32_LITTLE(constants + 0); + _state.i[1] = U8TO32_LITTLE(k + 0); + _state.i[2] = U8TO32_LITTLE(k + 4); + _state.i[3] = U8TO32_LITTLE(k + 8); + _state.i[4] = U8TO32_LITTLE(k + 12); + _state.i[5] = U8TO32_LITTLE(constants + 4); + _state.i[6] = U8TO32_LITTLE(((const uint8_t *)iv) + 0); + _state.i[7] = U8TO32_LITTLE(((const uint8_t *)iv) + 4); + _state.i[8] = 0; + _state.i[9] = 0; + _state.i[10] = U8TO32_LITTLE(constants + 8); + _state.i[11] = U8TO32_LITTLE(k + 16); + _state.i[12] = U8TO32_LITTLE(k + 20); + _state.i[13] = U8TO32_LITTLE(k + 24); + _state.i[14] = U8TO32_LITTLE(k + 28); + _state.i[15] = U8TO32_LITTLE(constants + 12); +#endif +} + +void Salsa20::crypt12(const void *in,void *out,unsigned int bytes) +{ + uint8_t tmp[64]; + const uint8_t *m = (const uint8_t *)in; + uint8_t *c = (uint8_t *)out; + uint8_t *ctarget = c; + unsigned int i; + +#ifndef ZT_SALSA20_SSE + uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; +#endif + + if (!bytes) + return; + +#ifndef ZT_SALSA20_SSE + j0 = _state.i[0]; + j1 = _state.i[1]; + j2 = _state.i[2]; + j3 = _state.i[3]; + j4 = _state.i[4]; + j5 = _state.i[5]; + j6 = _state.i[6]; + j7 = _state.i[7]; + j8 = _state.i[8]; + j9 = _state.i[9]; + j10 = _state.i[10]; + j11 = _state.i[11]; + j12 = _state.i[12]; + j13 = _state.i[13]; + j14 = _state.i[14]; + j15 = _state.i[15]; +#endif + + for (;;) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) + tmp[i] = m[i]; + m = tmp; + ctarget = c; + c = tmp; + } + +#ifdef ZT_SALSA20_SSE + __m128i X0 = _mm_loadu_si128((const __m128i *)&(_state.v[0])); + __m128i X1 = _mm_loadu_si128((const __m128i *)&(_state.v[1])); + __m128i X2 = _mm_loadu_si128((const __m128i *)&(_state.v[2])); + __m128i X3 = _mm_loadu_si128((const __m128i *)&(_state.v[3])); + __m128i T; + __m128i X0s = X0; + __m128i X1s = X1; + __m128i X2s = X2; + __m128i X3s = X3; + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + X0 = _mm_add_epi32(X0s,X0); + X1 = _mm_add_epi32(X1s,X1); + X2 = _mm_add_epi32(X2s,X2); + X3 = _mm_add_epi32(X3s,X3); + + __m128i k02 = _mm_shuffle_epi32(_mm_or_si128(_mm_slli_epi64(X0, 32), _mm_srli_epi64(X3, 32)), _MM_SHUFFLE(0, 1, 2, 3)); + __m128i k13 = _mm_shuffle_epi32(_mm_or_si128(_mm_slli_epi64(X1, 32), _mm_srli_epi64(X0, 32)), _MM_SHUFFLE(0, 1, 2, 3)); + __m128i k20 = _mm_or_si128(_mm_and_si128(X2, _S20SSECONSTANTS.maskLo32), _mm_and_si128(X1, _S20SSECONSTANTS.maskHi32)); + __m128i k31 = _mm_or_si128(_mm_and_si128(X3, _S20SSECONSTANTS.maskLo32), _mm_and_si128(X2, _S20SSECONSTANTS.maskHi32)); + _mm_storeu_ps(reinterpret_cast(c),_mm_castsi128_ps(_mm_xor_si128(_mm_unpackhi_epi64(k02,k20),_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(m)))))); + _mm_storeu_ps(reinterpret_cast(c) + 4,_mm_castsi128_ps(_mm_xor_si128(_mm_unpackhi_epi64(k13,k31),_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(m) + 4))))); + _mm_storeu_ps(reinterpret_cast(c) + 8,_mm_castsi128_ps(_mm_xor_si128(_mm_unpacklo_epi64(k20,k02),_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(m) + 8))))); + _mm_storeu_ps(reinterpret_cast(c) + 12,_mm_castsi128_ps(_mm_xor_si128(_mm_unpacklo_epi64(k31,k13),_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(m) + 12))))); + + if (!(++_state.i[8])) { + ++_state.i[5]; // state reordered for SSE + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } +#else + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + x0 = PLUS(x0,j0); + x1 = PLUS(x1,j1); + x2 = PLUS(x2,j2); + x3 = PLUS(x3,j3); + x4 = PLUS(x4,j4); + x5 = PLUS(x5,j5); + x6 = PLUS(x6,j6); + x7 = PLUS(x7,j7); + x8 = PLUS(x8,j8); + x9 = PLUS(x9,j9); + x10 = PLUS(x10,j10); + x11 = PLUS(x11,j11); + x12 = PLUS(x12,j12); + x13 = PLUS(x13,j13); + x14 = PLUS(x14,j14); + x15 = PLUS(x15,j15); + + U32TO8_LITTLE(c + 0,XOR(x0,U8TO32_LITTLE(m + 0))); + U32TO8_LITTLE(c + 4,XOR(x1,U8TO32_LITTLE(m + 4))); + U32TO8_LITTLE(c + 8,XOR(x2,U8TO32_LITTLE(m + 8))); + U32TO8_LITTLE(c + 12,XOR(x3,U8TO32_LITTLE(m + 12))); + U32TO8_LITTLE(c + 16,XOR(x4,U8TO32_LITTLE(m + 16))); + U32TO8_LITTLE(c + 20,XOR(x5,U8TO32_LITTLE(m + 20))); + U32TO8_LITTLE(c + 24,XOR(x6,U8TO32_LITTLE(m + 24))); + U32TO8_LITTLE(c + 28,XOR(x7,U8TO32_LITTLE(m + 28))); + U32TO8_LITTLE(c + 32,XOR(x8,U8TO32_LITTLE(m + 32))); + U32TO8_LITTLE(c + 36,XOR(x9,U8TO32_LITTLE(m + 36))); + U32TO8_LITTLE(c + 40,XOR(x10,U8TO32_LITTLE(m + 40))); + U32TO8_LITTLE(c + 44,XOR(x11,U8TO32_LITTLE(m + 44))); + U32TO8_LITTLE(c + 48,XOR(x12,U8TO32_LITTLE(m + 48))); + U32TO8_LITTLE(c + 52,XOR(x13,U8TO32_LITTLE(m + 52))); + U32TO8_LITTLE(c + 56,XOR(x14,U8TO32_LITTLE(m + 56))); + U32TO8_LITTLE(c + 60,XOR(x15,U8TO32_LITTLE(m + 60))); + + if (!(++j8)) { + ++j9; + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } +#endif + + if (bytes <= 64) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) + ctarget[i] = c[i]; + } + +#ifndef ZT_SALSA20_SSE + _state.i[8] = j8; + _state.i[9] = j9; +#endif + + return; + } + + bytes -= 64; + c += 64; + m += 64; + } +} + +void Salsa20::crypt20(const void *in,void *out,unsigned int bytes) +{ + uint8_t tmp[64]; + const uint8_t *m = (const uint8_t *)in; + uint8_t *c = (uint8_t *)out; + uint8_t *ctarget = c; + unsigned int i; + +#ifndef ZT_SALSA20_SSE + uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; +#endif + + if (!bytes) + return; + +#ifndef ZT_SALSA20_SSE + j0 = _state.i[0]; + j1 = _state.i[1]; + j2 = _state.i[2]; + j3 = _state.i[3]; + j4 = _state.i[4]; + j5 = _state.i[5]; + j6 = _state.i[6]; + j7 = _state.i[7]; + j8 = _state.i[8]; + j9 = _state.i[9]; + j10 = _state.i[10]; + j11 = _state.i[11]; + j12 = _state.i[12]; + j13 = _state.i[13]; + j14 = _state.i[14]; + j15 = _state.i[15]; +#endif + + for (;;) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) + tmp[i] = m[i]; + m = tmp; + ctarget = c; + c = tmp; + } + +#ifdef ZT_SALSA20_SSE + __m128i X0 = _mm_loadu_si128((const __m128i *)&(_state.v[0])); + __m128i X1 = _mm_loadu_si128((const __m128i *)&(_state.v[1])); + __m128i X2 = _mm_loadu_si128((const __m128i *)&(_state.v[2])); + __m128i X3 = _mm_loadu_si128((const __m128i *)&(_state.v[3])); + __m128i T; + __m128i X0s = X0; + __m128i X1s = X1; + __m128i X2s = X2; + __m128i X3s = X3; + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + // 2X round ------------------------------------------------------------- + T = _mm_add_epi32(X0, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X1, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X3, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x93); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x39); + T = _mm_add_epi32(X0, X1); + X3 = _mm_xor_si128(_mm_xor_si128(X3, _mm_slli_epi32(T, 7)), _mm_srli_epi32(T, 25)); + T = _mm_add_epi32(X3, X0); + X2 = _mm_xor_si128(_mm_xor_si128(X2, _mm_slli_epi32(T, 9)), _mm_srli_epi32(T, 23)); + T = _mm_add_epi32(X2, X3); + X1 = _mm_xor_si128(_mm_xor_si128(X1, _mm_slli_epi32(T, 13)), _mm_srli_epi32(T, 19)); + T = _mm_add_epi32(X1, X2); + X0 = _mm_xor_si128(_mm_xor_si128(X0, _mm_slli_epi32(T, 18)), _mm_srli_epi32(T, 14)); + X1 = _mm_shuffle_epi32(X1, 0x39); + X2 = _mm_shuffle_epi32(X2, 0x4E); + X3 = _mm_shuffle_epi32(X3, 0x93); + + X0 = _mm_add_epi32(X0s,X0); + X1 = _mm_add_epi32(X1s,X1); + X2 = _mm_add_epi32(X2s,X2); + X3 = _mm_add_epi32(X3s,X3); + + __m128i k02 = _mm_shuffle_epi32(_mm_or_si128(_mm_slli_epi64(X0, 32), _mm_srli_epi64(X3, 32)), _MM_SHUFFLE(0, 1, 2, 3)); + __m128i k13 = _mm_shuffle_epi32(_mm_or_si128(_mm_slli_epi64(X1, 32), _mm_srli_epi64(X0, 32)), _MM_SHUFFLE(0, 1, 2, 3)); + __m128i k20 = _mm_or_si128(_mm_and_si128(X2, _S20SSECONSTANTS.maskLo32), _mm_and_si128(X1, _S20SSECONSTANTS.maskHi32)); + __m128i k31 = _mm_or_si128(_mm_and_si128(X3, _S20SSECONSTANTS.maskLo32), _mm_and_si128(X2, _S20SSECONSTANTS.maskHi32)); + _mm_storeu_ps(reinterpret_cast(c),_mm_castsi128_ps(_mm_xor_si128(_mm_unpackhi_epi64(k02,k20),_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(m)))))); + _mm_storeu_ps(reinterpret_cast(c) + 4,_mm_castsi128_ps(_mm_xor_si128(_mm_unpackhi_epi64(k13,k31),_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(m) + 4))))); + _mm_storeu_ps(reinterpret_cast(c) + 8,_mm_castsi128_ps(_mm_xor_si128(_mm_unpacklo_epi64(k20,k02),_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(m) + 8))))); + _mm_storeu_ps(reinterpret_cast(c) + 12,_mm_castsi128_ps(_mm_xor_si128(_mm_unpacklo_epi64(k31,k13),_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(m) + 12))))); + + if (!(++_state.i[8])) { + ++_state.i[5]; // state reordered for SSE + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } +#else + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + // 2X round ------------------------------------------------------------- + x4 = XOR( x4,ROTATE(PLUS( x0,x12), 7)); + x8 = XOR( x8,ROTATE(PLUS( x4, x0), 9)); + x12 = XOR(x12,ROTATE(PLUS( x8, x4),13)); + x0 = XOR( x0,ROTATE(PLUS(x12, x8),18)); + x9 = XOR( x9,ROTATE(PLUS( x5, x1), 7)); + x13 = XOR(x13,ROTATE(PLUS( x9, x5), 9)); + x1 = XOR( x1,ROTATE(PLUS(x13, x9),13)); + x5 = XOR( x5,ROTATE(PLUS( x1,x13),18)); + x14 = XOR(x14,ROTATE(PLUS(x10, x6), 7)); + x2 = XOR( x2,ROTATE(PLUS(x14,x10), 9)); + x6 = XOR( x6,ROTATE(PLUS( x2,x14),13)); + x10 = XOR(x10,ROTATE(PLUS( x6, x2),18)); + x3 = XOR( x3,ROTATE(PLUS(x15,x11), 7)); + x7 = XOR( x7,ROTATE(PLUS( x3,x15), 9)); + x11 = XOR(x11,ROTATE(PLUS( x7, x3),13)); + x15 = XOR(x15,ROTATE(PLUS(x11, x7),18)); + x1 = XOR( x1,ROTATE(PLUS( x0, x3), 7)); + x2 = XOR( x2,ROTATE(PLUS( x1, x0), 9)); + x3 = XOR( x3,ROTATE(PLUS( x2, x1),13)); + x0 = XOR( x0,ROTATE(PLUS( x3, x2),18)); + x6 = XOR( x6,ROTATE(PLUS( x5, x4), 7)); + x7 = XOR( x7,ROTATE(PLUS( x6, x5), 9)); + x4 = XOR( x4,ROTATE(PLUS( x7, x6),13)); + x5 = XOR( x5,ROTATE(PLUS( x4, x7),18)); + x11 = XOR(x11,ROTATE(PLUS(x10, x9), 7)); + x8 = XOR( x8,ROTATE(PLUS(x11,x10), 9)); + x9 = XOR( x9,ROTATE(PLUS( x8,x11),13)); + x10 = XOR(x10,ROTATE(PLUS( x9, x8),18)); + x12 = XOR(x12,ROTATE(PLUS(x15,x14), 7)); + x13 = XOR(x13,ROTATE(PLUS(x12,x15), 9)); + x14 = XOR(x14,ROTATE(PLUS(x13,x12),13)); + x15 = XOR(x15,ROTATE(PLUS(x14,x13),18)); + + x0 = PLUS(x0,j0); + x1 = PLUS(x1,j1); + x2 = PLUS(x2,j2); + x3 = PLUS(x3,j3); + x4 = PLUS(x4,j4); + x5 = PLUS(x5,j5); + x6 = PLUS(x6,j6); + x7 = PLUS(x7,j7); + x8 = PLUS(x8,j8); + x9 = PLUS(x9,j9); + x10 = PLUS(x10,j10); + x11 = PLUS(x11,j11); + x12 = PLUS(x12,j12); + x13 = PLUS(x13,j13); + x14 = PLUS(x14,j14); + x15 = PLUS(x15,j15); + + U32TO8_LITTLE(c + 0,XOR(x0,U8TO32_LITTLE(m + 0))); + U32TO8_LITTLE(c + 4,XOR(x1,U8TO32_LITTLE(m + 4))); + U32TO8_LITTLE(c + 8,XOR(x2,U8TO32_LITTLE(m + 8))); + U32TO8_LITTLE(c + 12,XOR(x3,U8TO32_LITTLE(m + 12))); + U32TO8_LITTLE(c + 16,XOR(x4,U8TO32_LITTLE(m + 16))); + U32TO8_LITTLE(c + 20,XOR(x5,U8TO32_LITTLE(m + 20))); + U32TO8_LITTLE(c + 24,XOR(x6,U8TO32_LITTLE(m + 24))); + U32TO8_LITTLE(c + 28,XOR(x7,U8TO32_LITTLE(m + 28))); + U32TO8_LITTLE(c + 32,XOR(x8,U8TO32_LITTLE(m + 32))); + U32TO8_LITTLE(c + 36,XOR(x9,U8TO32_LITTLE(m + 36))); + U32TO8_LITTLE(c + 40,XOR(x10,U8TO32_LITTLE(m + 40))); + U32TO8_LITTLE(c + 44,XOR(x11,U8TO32_LITTLE(m + 44))); + U32TO8_LITTLE(c + 48,XOR(x12,U8TO32_LITTLE(m + 48))); + U32TO8_LITTLE(c + 52,XOR(x13,U8TO32_LITTLE(m + 52))); + U32TO8_LITTLE(c + 56,XOR(x14,U8TO32_LITTLE(m + 56))); + U32TO8_LITTLE(c + 60,XOR(x15,U8TO32_LITTLE(m + 60))); + + if (!(++j8)) { + ++j9; + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } +#endif + + if (bytes <= 64) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) + ctarget[i] = c[i]; + } + +#ifndef ZT_SALSA20_SSE + _state.i[8] = j8; + _state.i[9] = j9; +#endif + + return; + } + + bytes -= 64; + c += 64; + m += 64; + } +} + +} // namespace ZeroTier diff --git a/node/Salsa20.hpp b/node/Salsa20.hpp new file mode 100644 index 0000000..5259260 --- /dev/null +++ b/node/Salsa20.hpp @@ -0,0 +1,120 @@ +/* + * Based on public domain code available at: http://cr.yp.to/snuffle.html + * + * This therefore is public domain. + */ + +#ifndef ZT_SALSA20_HPP +#define ZT_SALSA20_HPP + +#include +#include +#include +#include + +#include "Constants.hpp" +#include "Utils.hpp" + +#if (!defined(ZT_SALSA20_SSE)) && (defined(__SSE2__) || defined(__WINDOWS__)) +#define ZT_SALSA20_SSE 1 +#endif + +#ifdef ZT_SALSA20_SSE +#include +#endif // ZT_SALSA20_SSE + +namespace ZeroTier { + +/** + * Salsa20 stream cipher + */ +class Salsa20 +{ +public: + Salsa20() {} + ~Salsa20() { Utils::burn(&_state,sizeof(_state)); } + + /** + * XOR d with s + * + * This is done efficiently using e.g. SSE if available. It's used when + * alternative Salsa20 implementations are used in Packet and is here + * since this is where all the SSE stuff is already included. + * + * @param d Destination to XOR + * @param s Source bytes to XOR with destination + * @param len Length of s and d + */ + static inline void memxor(uint8_t *d,const uint8_t *s,unsigned int len) + { +#ifdef ZT_SALSA20_SSE + while (len >= 16) { + _mm_storeu_si128(reinterpret_cast<__m128i *>(d),_mm_xor_si128(_mm_loadu_si128(reinterpret_cast<__m128i *>(d)),_mm_loadu_si128(reinterpret_cast(s)))); + s += 16; + d += 16; + len -= 16; + } +#else +#ifndef ZT_NO_TYPE_PUNNING + while (len >= 16) { + (*reinterpret_cast(d)) ^= (*reinterpret_cast(s)); + s += 8; + d += 8; + (*reinterpret_cast(d)) ^= (*reinterpret_cast(s)); + s += 8; + d += 8; + len -= 16; + } +#endif +#endif + while (len--) + *(d++) ^= *(s++); + } + + /** + * @param key 256-bit (32 byte) key + * @param iv 64-bit initialization vector + */ + Salsa20(const void *key,const void *iv) + { + init(key,iv); + } + + /** + * Initialize cipher + * + * @param key Key bits + * @param iv 64-bit initialization vector + */ + void init(const void *key,const void *iv); + + /** + * Encrypt/decrypt data using Salsa20/12 + * + * @param in Input data + * @param out Output buffer + * @param bytes Length of data + */ + void crypt12(const void *in,void *out,unsigned int bytes); + + /** + * Encrypt/decrypt data using Salsa20/20 + * + * @param in Input data + * @param out Output buffer + * @param bytes Length of data + */ + void crypt20(const void *in,void *out,unsigned int bytes); + +private: + union { +#ifdef ZT_SALSA20_SSE + __m128i v[4]; +#endif // ZT_SALSA20_SSE + uint32_t i[16]; + } _state; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/SelfAwareness.cpp b/node/SelfAwareness.cpp new file mode 100644 index 0000000..cba84cd --- /dev/null +++ b/node/SelfAwareness.cpp @@ -0,0 +1,195 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include + +#include +#include + +#include "Constants.hpp" +#include "SelfAwareness.hpp" +#include "RuntimeEnvironment.hpp" +#include "Node.hpp" +#include "Topology.hpp" +#include "Packet.hpp" +#include "Peer.hpp" +#include "Switch.hpp" + +// Entry timeout -- make it fairly long since this is just to prevent stale buildup +#define ZT_SELFAWARENESS_ENTRY_TIMEOUT 600000 + +namespace ZeroTier { + +class _ResetWithinScope +{ +public: + _ResetWithinScope(void *tPtr,uint64_t now,int inetAddressFamily,InetAddress::IpScope scope) : + _now(now), + _tPtr(tPtr), + _family(inetAddressFamily), + _scope(scope) {} + + inline void operator()(Topology &t,const SharedPtr &p) { p->resetWithinScope(_tPtr,_scope,_family,_now); } + +private: + uint64_t _now; + void *_tPtr; + int _family; + InetAddress::IpScope _scope; +}; + +SelfAwareness::SelfAwareness(const RuntimeEnvironment *renv) : + RR(renv), + _phy(128) +{ +} + +void SelfAwareness::iam(void *tPtr,const Address &reporter,const InetAddress &receivedOnLocalAddress,const InetAddress &reporterPhysicalAddress,const InetAddress &myPhysicalAddress,bool trusted,uint64_t now) +{ + const InetAddress::IpScope scope = myPhysicalAddress.ipScope(); + + if ((scope != reporterPhysicalAddress.ipScope())||(scope == InetAddress::IP_SCOPE_NONE)||(scope == InetAddress::IP_SCOPE_LOOPBACK)||(scope == InetAddress::IP_SCOPE_MULTICAST)) + return; + + Mutex::Lock _l(_phy_m); + PhySurfaceEntry &entry = _phy[PhySurfaceKey(reporter,receivedOnLocalAddress,reporterPhysicalAddress,scope)]; + + if ( (trusted) && ((now - entry.ts) < ZT_SELFAWARENESS_ENTRY_TIMEOUT) && (!entry.mySurface.ipsEqual(myPhysicalAddress)) ) { + // Changes to external surface reported by trusted peers causes path reset in this scope + TRACE("physical address %s for scope %u as seen from %s(%s) differs from %s, resetting paths in scope",myPhysicalAddress.toString().c_str(),(unsigned int)scope,reporter.toString().c_str(),reporterPhysicalAddress.toString().c_str(),entry.mySurface.toString().c_str()); + + entry.mySurface = myPhysicalAddress; + entry.ts = now; + entry.trusted = trusted; + + // Erase all entries in this scope that were not reported from this remote address to prevent 'thrashing' + // due to multiple reports of endpoint change. + // Don't use 'entry' after this since hash table gets modified. + { + Hashtable< PhySurfaceKey,PhySurfaceEntry >::Iterator i(_phy); + PhySurfaceKey *k = (PhySurfaceKey *)0; + PhySurfaceEntry *e = (PhySurfaceEntry *)0; + while (i.next(k,e)) { + if ((k->reporterPhysicalAddress != reporterPhysicalAddress)&&(k->scope == scope)) + _phy.erase(*k); + } + } + + // Reset all paths within this scope and address family + _ResetWithinScope rset(tPtr,now,myPhysicalAddress.ss_family,(InetAddress::IpScope)scope); + RR->topology->eachPeer<_ResetWithinScope &>(rset); + } else { + // Otherwise just update DB to use to determine external surface info + entry.mySurface = myPhysicalAddress; + entry.ts = now; + entry.trusted = trusted; + } +} + +void SelfAwareness::clean(uint64_t now) +{ + Mutex::Lock _l(_phy_m); + Hashtable< PhySurfaceKey,PhySurfaceEntry >::Iterator i(_phy); + PhySurfaceKey *k = (PhySurfaceKey *)0; + PhySurfaceEntry *e = (PhySurfaceEntry *)0; + while (i.next(k,e)) { + if ((now - e->ts) >= ZT_SELFAWARENESS_ENTRY_TIMEOUT) + _phy.erase(*k); + } +} + +std::vector SelfAwareness::getSymmetricNatPredictions() +{ + /* This is based on ideas and strategies found here: + * https://tools.ietf.org/html/draft-takeda-symmetric-nat-traversal-00 + * + * For each IP address reported by a trusted (upstream) peer, we find + * the external port most recently reported by ANY peer for that IP. + * + * We only do any of this for global IPv4 addresses since private IPs + * and IPv6 are not going to have symmetric NAT. + * + * SECURITY NOTE: + * + * We never use IPs reported by non-trusted peers, since this could lead + * to a minor vulnerability whereby a peer could poison our cache with + * bad external surface reports via OK(HELLO) and then possibly coax us + * into suggesting their IP to other peers via PUSH_DIRECT_PATHS. This + * in turn could allow them to MITM flows. + * + * Since flows are encrypted and authenticated they could not actually + * read or modify traffic, but they could gather meta-data for forensics + * purpsoes or use this as a DOS attack vector. */ + + std::map< uint32_t,std::pair > maxPortByIp; + InetAddress theOneTrueSurface; + bool symmetric = false; + { + Mutex::Lock _l(_phy_m); + + { // First get IPs from only trusted peers, and perform basic NAT type characterization + Hashtable< PhySurfaceKey,PhySurfaceEntry >::Iterator i(_phy); + PhySurfaceKey *k = (PhySurfaceKey *)0; + PhySurfaceEntry *e = (PhySurfaceEntry *)0; + while (i.next(k,e)) { + if ((e->trusted)&&(e->mySurface.ss_family == AF_INET)&&(e->mySurface.ipScope() == InetAddress::IP_SCOPE_GLOBAL)) { + if (!theOneTrueSurface) + theOneTrueSurface = e->mySurface; + else if (theOneTrueSurface != e->mySurface) + symmetric = true; + maxPortByIp[reinterpret_cast(&(e->mySurface))->sin_addr.s_addr] = std::pair(e->ts,e->mySurface.port()); + } + } + } + + { // Then find max port per IP from a trusted peer + Hashtable< PhySurfaceKey,PhySurfaceEntry >::Iterator i(_phy); + PhySurfaceKey *k = (PhySurfaceKey *)0; + PhySurfaceEntry *e = (PhySurfaceEntry *)0; + while (i.next(k,e)) { + if ((e->mySurface.ss_family == AF_INET)&&(e->mySurface.ipScope() == InetAddress::IP_SCOPE_GLOBAL)) { + std::map< uint32_t,std::pair >::iterator mp(maxPortByIp.find(reinterpret_cast(&(e->mySurface))->sin_addr.s_addr)); + if ((mp != maxPortByIp.end())&&(mp->second.first < e->ts)) { + mp->second.first = e->ts; + mp->second.second = e->mySurface.port(); + } + } + } + } + } + + if (symmetric) { + std::vector r; + for(unsigned int k=1;k<=3;++k) { + for(std::map< uint32_t,std::pair >::iterator i(maxPortByIp.begin());i!=maxPortByIp.end();++i) { + unsigned int p = i->second.second + k; + if (p > 65535) p -= 64511; + InetAddress pred(&(i->first),4,p); + if (std::find(r.begin(),r.end(),pred) == r.end()) + r.push_back(pred); + } + } + return r; + } + + return std::vector(); +} + +} // namespace ZeroTier diff --git a/node/SelfAwareness.hpp b/node/SelfAwareness.hpp new file mode 100644 index 0000000..c1db0c8 --- /dev/null +++ b/node/SelfAwareness.hpp @@ -0,0 +1,98 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_SELFAWARENESS_HPP +#define ZT_SELFAWARENESS_HPP + +#include "Constants.hpp" +#include "InetAddress.hpp" +#include "Hashtable.hpp" +#include "Address.hpp" +#include "Mutex.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Tracks changes to this peer's real world addresses + */ +class SelfAwareness +{ +public: + SelfAwareness(const RuntimeEnvironment *renv); + + /** + * Called when a trusted remote peer informs us of our external network address + * + * @param reporter ZeroTier address of reporting peer + * @param receivedOnLocalAddress Local address on which report was received + * @param reporterPhysicalAddress Physical address that reporting peer seems to have + * @param myPhysicalAddress Physical address that peer says we have + * @param trusted True if this peer is trusted as an authority to inform us of external address changes + * @param now Current time + */ + void iam(void *tPtr,const Address &reporter,const InetAddress &receivedOnLocalAddress,const InetAddress &reporterPhysicalAddress,const InetAddress &myPhysicalAddress,bool trusted,uint64_t now); + + /** + * Clean up database periodically + * + * @param now Current time + */ + void clean(uint64_t now); + + /** + * If we appear to be behind a symmetric NAT, get predictions for possible external endpoints + * + * @return Symmetric NAT predictions or empty vector if none + */ + std::vector getSymmetricNatPredictions(); + +private: + struct PhySurfaceKey + { + Address reporter; + InetAddress receivedOnLocalAddress; + InetAddress reporterPhysicalAddress; + InetAddress::IpScope scope; + + PhySurfaceKey() : reporter(),scope(InetAddress::IP_SCOPE_NONE) {} + PhySurfaceKey(const Address &r,const InetAddress &rol,const InetAddress &ra,InetAddress::IpScope s) : reporter(r),receivedOnLocalAddress(rol),reporterPhysicalAddress(ra),scope(s) {} + + inline unsigned long hashCode() const throw() { return ((unsigned long)reporter.toInt() + (unsigned long)scope); } + inline bool operator==(const PhySurfaceKey &k) const throw() { return ((reporter == k.reporter)&&(receivedOnLocalAddress == k.receivedOnLocalAddress)&&(reporterPhysicalAddress == k.reporterPhysicalAddress)&&(scope == k.scope)); } + }; + struct PhySurfaceEntry + { + InetAddress mySurface; + uint64_t ts; + bool trusted; + + PhySurfaceEntry() : mySurface(),ts(0),trusted(false) {} + PhySurfaceEntry(const InetAddress &a,const uint64_t t) : mySurface(a),ts(t),trusted(false) {} + }; + + const RuntimeEnvironment *RR; + + Hashtable< PhySurfaceKey,PhySurfaceEntry > _phy; + Mutex _phy_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/SharedPtr.hpp b/node/SharedPtr.hpp new file mode 100644 index 0000000..1dd3b43 --- /dev/null +++ b/node/SharedPtr.hpp @@ -0,0 +1,178 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_SHAREDPTR_HPP +#define ZT_SHAREDPTR_HPP + +#include "Mutex.hpp" +#include "AtomicCounter.hpp" + +namespace ZeroTier { + +/** + * Simple reference counted pointer + * + * This is an introspective shared pointer. Classes that need to be reference + * counted must list this as a 'friend' and must have a private instance of + * AtomicCounter called __refCount. They should also have private destructors, + * since only this class should delete them. + * + * Because this is introspective, it is safe to apply to a naked pointer + * multiple times provided there is always at least one holding SharedPtr. + * + * Once C++11 is ubiquitous, this and a few other things like Thread might get + * torn out for their standard equivalents. + */ +template +class SharedPtr +{ +public: + SharedPtr() + throw() : + _ptr((T *)0) + { + } + + SharedPtr(T *obj) + throw() : + _ptr(obj) + { + ++obj->__refCount; + } + + SharedPtr(const SharedPtr &sp) + throw() : + _ptr(sp._getAndInc()) + { + } + + ~SharedPtr() + { + if (_ptr) { + if (--_ptr->__refCount <= 0) + delete _ptr; + } + } + + inline SharedPtr &operator=(const SharedPtr &sp) + { + if (_ptr != sp._ptr) { + T *p = sp._getAndInc(); + if (_ptr) { + if (--_ptr->__refCount <= 0) + delete _ptr; + } + _ptr = p; + } + return *this; + } + + /** + * Set to a naked pointer and increment its reference count + * + * This assumes this SharedPtr is NULL and that ptr is not a 'zombie.' No + * checks are performed. + * + * @param ptr Naked pointer to assign + */ + inline void setToUnsafe(T *ptr) + { + ++ptr->__refCount; + _ptr = ptr; + } + + /** + * Swap with another pointer 'for free' without ref count overhead + * + * @param with Pointer to swap with + */ + inline void swap(SharedPtr &with) + throw() + { + T *tmp = _ptr; + _ptr = with._ptr; + with._ptr = tmp; + } + + inline operator bool() const throw() { return (_ptr != (T *)0); } + inline T &operator*() const throw() { return *_ptr; } + inline T *operator->() const throw() { return _ptr; } + + /** + * @return Raw pointer to held object + */ + inline T *ptr() const throw() { return _ptr; } + + /** + * Set this pointer to NULL + */ + inline void zero() + { + if (_ptr) { + if (--_ptr->__refCount <= 0) + delete _ptr; + _ptr = (T *)0; + } + } + + /** + * Set this pointer to NULL if this is the only pointer holding the object + * + * @return True if object was deleted and SharedPtr is now NULL (or was already NULL) + */ + inline bool reclaimIfWeak() + { + if (_ptr) { + if (++_ptr->__refCount <= 2) { + if (--_ptr->__refCount <= 1) { + delete _ptr; + _ptr = (T *)0; + return true; + } else { + return false; + } + } else { + return false; + } + } else { + return true; + } + } + + inline bool operator==(const SharedPtr &sp) const throw() { return (_ptr == sp._ptr); } + inline bool operator!=(const SharedPtr &sp) const throw() { return (_ptr != sp._ptr); } + inline bool operator>(const SharedPtr &sp) const throw() { return (_ptr > sp._ptr); } + inline bool operator<(const SharedPtr &sp) const throw() { return (_ptr < sp._ptr); } + inline bool operator>=(const SharedPtr &sp) const throw() { return (_ptr >= sp._ptr); } + inline bool operator<=(const SharedPtr &sp) const throw() { return (_ptr <= sp._ptr); } + +private: + inline T *_getAndInc() const + throw() + { + if (_ptr) + ++_ptr->__refCount; + return _ptr; + } + + T *_ptr; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Switch.cpp b/node/Switch.cpp new file mode 100644 index 0000000..56299a9 --- /dev/null +++ b/node/Switch.cpp @@ -0,0 +1,881 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include + +#include +#include +#include + +#include "../version.h" +#include "../include/ZeroTierOne.h" + +#include "Constants.hpp" +#include "RuntimeEnvironment.hpp" +#include "Switch.hpp" +#include "Node.hpp" +#include "InetAddress.hpp" +#include "Topology.hpp" +#include "Peer.hpp" +#include "SelfAwareness.hpp" +#include "Packet.hpp" +#include "Cluster.hpp" + +namespace ZeroTier { + +#ifdef ZT_TRACE +static const char *etherTypeName(const unsigned int etherType) +{ + switch(etherType) { + case ZT_ETHERTYPE_IPV4: return "IPV4"; + case ZT_ETHERTYPE_ARP: return "ARP"; + case ZT_ETHERTYPE_RARP: return "RARP"; + case ZT_ETHERTYPE_ATALK: return "ATALK"; + case ZT_ETHERTYPE_AARP: return "AARP"; + case ZT_ETHERTYPE_IPX_A: return "IPX_A"; + case ZT_ETHERTYPE_IPX_B: return "IPX_B"; + case ZT_ETHERTYPE_IPV6: return "IPV6"; + } + return "UNKNOWN"; +} +#endif // ZT_TRACE + +Switch::Switch(const RuntimeEnvironment *renv) : + RR(renv), + _lastBeaconResponse(0), + _outstandingWhoisRequests(32), + _lastUniteAttempt(8) // only really used on root servers and upstreams, and it'll grow there just fine +{ +} + +void Switch::onRemotePacket(void *tPtr,const InetAddress &localAddr,const InetAddress &fromAddr,const void *data,unsigned int len) +{ + try { + const uint64_t now = RR->node->now(); + + SharedPtr path(RR->topology->getPath(localAddr,fromAddr)); + path->received(now); + + if (len == 13) { + /* LEGACY: before VERB_PUSH_DIRECT_PATHS, peers used broadcast + * announcements on the LAN to solve the 'same network problem.' We + * no longer send these, but we'll listen for them for a while to + * locate peers with versions <1.0.4. */ + + const Address beaconAddr(reinterpret_cast(data) + 8,5); + if (beaconAddr == RR->identity.address()) + return; + if (!RR->node->shouldUsePathForZeroTierTraffic(tPtr,beaconAddr,localAddr,fromAddr)) + return; + const SharedPtr peer(RR->topology->getPeer(tPtr,beaconAddr)); + if (peer) { // we'll only respond to beacons from known peers + if ((now - _lastBeaconResponse) >= 2500) { // limit rate of responses + _lastBeaconResponse = now; + Packet outp(peer->address(),RR->identity.address(),Packet::VERB_NOP); + outp.armor(peer->key(),true,path->nextOutgoingCounter()); + path->send(RR,tPtr,outp.data(),outp.size(),now); + } + } + + } else if (len > ZT_PROTO_MIN_FRAGMENT_LENGTH) { // SECURITY: min length check is important since we do some C-style stuff below! + if (reinterpret_cast(data)[ZT_PACKET_FRAGMENT_IDX_FRAGMENT_INDICATOR] == ZT_PACKET_FRAGMENT_INDICATOR) { + // Handle fragment ---------------------------------------------------- + + Packet::Fragment fragment(data,len); + const Address destination(fragment.destination()); + + if (destination != RR->identity.address()) { +#ifdef ZT_ENABLE_CLUSTER + const bool isClusterFrontplane = ((RR->cluster)&&(RR->cluster->isClusterPeerFrontplane(fromAddr))); +#else + const bool isClusterFrontplane = false; +#endif + + if ( (!RR->topology->amRoot()) && (!path->trustEstablished(now)) && (!isClusterFrontplane) ) + return; + + if (fragment.hops() < ZT_RELAY_MAX_HOPS) { + fragment.incrementHops(); + + // Note: we don't bother initiating NAT-t for fragments, since heads will set that off. + // It wouldn't hurt anything, just redundant and unnecessary. + SharedPtr relayTo = RR->topology->getPeer(tPtr,destination); + if ((!relayTo)||(!relayTo->sendDirect(tPtr,fragment.data(),fragment.size(),now,false))) { +#ifdef ZT_ENABLE_CLUSTER + if ((RR->cluster)&&(!isClusterFrontplane)) { + RR->cluster->relayViaCluster(Address(),destination,fragment.data(),fragment.size(),false); + return; + } +#endif + + // Don't know peer or no direct path -- so relay via someone upstream + relayTo = RR->topology->getUpstreamPeer(); + if (relayTo) + relayTo->sendDirect(tPtr,fragment.data(),fragment.size(),now,true); + } + } else { + TRACE("dropped relay [fragment](%s) -> %s, max hops exceeded",fromAddr.toString().c_str(),destination.toString().c_str()); + } + } else { + // Fragment looks like ours + const uint64_t fragmentPacketId = fragment.packetId(); + const unsigned int fragmentNumber = fragment.fragmentNumber(); + const unsigned int totalFragments = fragment.totalFragments(); + + if ((totalFragments <= ZT_MAX_PACKET_FRAGMENTS)&&(fragmentNumber < ZT_MAX_PACKET_FRAGMENTS)&&(fragmentNumber > 0)&&(totalFragments > 1)) { + // Fragment appears basically sane. Its fragment number must be + // 1 or more, since a Packet with fragmented bit set is fragment 0. + // Total fragments must be more than 1, otherwise why are we + // seeing a Packet::Fragment? + + Mutex::Lock _l(_rxQueue_m); + RXQueueEntry *const rq = _findRXQueueEntry(now,fragmentPacketId); + + if ((!rq->timestamp)||(rq->packetId != fragmentPacketId)) { + // No packet found, so we received a fragment without its head. + //TRACE("fragment (%u/%u) of %.16llx from %s",fragmentNumber + 1,totalFragments,fragmentPacketId,fromAddr.toString().c_str()); + + rq->timestamp = now; + rq->packetId = fragmentPacketId; + rq->frags[fragmentNumber - 1] = fragment; + rq->totalFragments = totalFragments; // total fragment count is known + rq->haveFragments = 1 << fragmentNumber; // we have only this fragment + rq->complete = false; + } else if (!(rq->haveFragments & (1 << fragmentNumber))) { + // We have other fragments and maybe the head, so add this one and check + //TRACE("fragment (%u/%u) of %.16llx from %s",fragmentNumber + 1,totalFragments,fragmentPacketId,fromAddr.toString().c_str()); + + rq->frags[fragmentNumber - 1] = fragment; + rq->totalFragments = totalFragments; + + if (Utils::countBits(rq->haveFragments |= (1 << fragmentNumber)) == totalFragments) { + // We have all fragments -- assemble and process full Packet + //TRACE("packet %.16llx is complete, assembling and processing...",fragmentPacketId); + + for(unsigned int f=1;ffrag0.append(rq->frags[f - 1].payload(),rq->frags[f - 1].payloadLength()); + + if (rq->frag0.tryDecode(RR,tPtr)) { + rq->timestamp = 0; // packet decoded, free entry + } else { + rq->complete = true; // set complete flag but leave entry since it probably needs WHOIS or something + } + } + } // else this is a duplicate fragment, ignore + } + } + + // -------------------------------------------------------------------- + } else if (len >= ZT_PROTO_MIN_PACKET_LENGTH) { // min length check is important! + // Handle packet head ------------------------------------------------- + + const Address destination(reinterpret_cast(data) + 8,ZT_ADDRESS_LENGTH); + const Address source(reinterpret_cast(data) + 13,ZT_ADDRESS_LENGTH); + + //TRACE("<< %.16llx %s -> %s (size: %u)",(unsigned long long)packet->packetId(),source.toString().c_str(),destination.toString().c_str(),packet->size()); + +#ifdef ZT_ENABLE_CLUSTER + if ( (source == RR->identity.address()) && ((!RR->cluster)||(!RR->cluster->isClusterPeerFrontplane(fromAddr))) ) + return; +#else + if (source == RR->identity.address()) + return; +#endif + + if (destination != RR->identity.address()) { + if ( (!RR->topology->amRoot()) && (!path->trustEstablished(now)) && (source != RR->identity.address()) ) + return; + + Packet packet(data,len); + + if (packet.hops() < ZT_RELAY_MAX_HOPS) { +#ifdef ZT_ENABLE_CLUSTER + if (source != RR->identity.address()) // don't increment hops for cluster frontplane relays + packet.incrementHops(); +#else + packet.incrementHops(); +#endif + + SharedPtr relayTo = RR->topology->getPeer(tPtr,destination); + if ((relayTo)&&(relayTo->sendDirect(tPtr,packet.data(),packet.size(),now,false))) { + if ((source != RR->identity.address())&&(_shouldUnite(now,source,destination))) { // don't send RENDEZVOUS for cluster frontplane relays + const InetAddress *hintToSource = (InetAddress *)0; + const InetAddress *hintToDest = (InetAddress *)0; + + InetAddress destV4,destV6; + InetAddress sourceV4,sourceV6; + relayTo->getRendezvousAddresses(now,destV4,destV6); + + const SharedPtr sourcePeer(RR->topology->getPeer(tPtr,source)); + if (sourcePeer) { + sourcePeer->getRendezvousAddresses(now,sourceV4,sourceV6); + if ((destV6)&&(sourceV6)) { + hintToSource = &destV6; + hintToDest = &sourceV6; + } else if ((destV4)&&(sourceV4)) { + hintToSource = &destV4; + hintToDest = &sourceV4; + } + + if ((hintToSource)&&(hintToDest)) { + unsigned int alt = (unsigned int)RR->node->prng() & 1; // randomize which hint we send first for obscure NAT-t reasons + const unsigned int completed = alt + 2; + while (alt != completed) { + if ((alt & 1) == 0) { + Packet outp(source,RR->identity.address(),Packet::VERB_RENDEZVOUS); + outp.append((uint8_t)0); + destination.appendTo(outp); + outp.append((uint16_t)hintToSource->port()); + if (hintToSource->ss_family == AF_INET6) { + outp.append((uint8_t)16); + outp.append(hintToSource->rawIpData(),16); + } else { + outp.append((uint8_t)4); + outp.append(hintToSource->rawIpData(),4); + } + send(tPtr,outp,true); + } else { + Packet outp(destination,RR->identity.address(),Packet::VERB_RENDEZVOUS); + outp.append((uint8_t)0); + source.appendTo(outp); + outp.append((uint16_t)hintToDest->port()); + if (hintToDest->ss_family == AF_INET6) { + outp.append((uint8_t)16); + outp.append(hintToDest->rawIpData(),16); + } else { + outp.append((uint8_t)4); + outp.append(hintToDest->rawIpData(),4); + } + send(tPtr,outp,true); + } + ++alt; + } + } + } + } + } else { +#ifdef ZT_ENABLE_CLUSTER + if ((RR->cluster)&&(source != RR->identity.address())) { + RR->cluster->relayViaCluster(source,destination,packet.data(),packet.size(),_shouldUnite(now,source,destination)); + return; + } +#endif + relayTo = RR->topology->getUpstreamPeer(&source,1,true); + if (relayTo) + relayTo->sendDirect(tPtr,packet.data(),packet.size(),now,true); + } + } else { + TRACE("dropped relay %s(%s) -> %s, max hops exceeded",packet.source().toString().c_str(),fromAddr.toString().c_str(),destination.toString().c_str()); + } + } else if ((reinterpret_cast(data)[ZT_PACKET_IDX_FLAGS] & ZT_PROTO_FLAG_FRAGMENTED) != 0) { + // Packet is the head of a fragmented packet series + + const uint64_t packetId = ( + (((uint64_t)reinterpret_cast(data)[0]) << 56) | + (((uint64_t)reinterpret_cast(data)[1]) << 48) | + (((uint64_t)reinterpret_cast(data)[2]) << 40) | + (((uint64_t)reinterpret_cast(data)[3]) << 32) | + (((uint64_t)reinterpret_cast(data)[4]) << 24) | + (((uint64_t)reinterpret_cast(data)[5]) << 16) | + (((uint64_t)reinterpret_cast(data)[6]) << 8) | + ((uint64_t)reinterpret_cast(data)[7]) + ); + + Mutex::Lock _l(_rxQueue_m); + RXQueueEntry *const rq = _findRXQueueEntry(now,packetId); + + if ((!rq->timestamp)||(rq->packetId != packetId)) { + // If we have no other fragments yet, create an entry and save the head + //TRACE("fragment (0/?) of %.16llx from %s",pid,fromAddr.toString().c_str()); + + rq->timestamp = now; + rq->packetId = packetId; + rq->frag0.init(data,len,path,now); + rq->totalFragments = 0; + rq->haveFragments = 1; + rq->complete = false; + } else if (!(rq->haveFragments & 1)) { + // If we have other fragments but no head, see if we are complete with the head + + if ((rq->totalFragments > 1)&&(Utils::countBits(rq->haveFragments |= 1) == rq->totalFragments)) { + // We have all fragments -- assemble and process full Packet + //TRACE("packet %.16llx is complete, assembling and processing...",pid); + + rq->frag0.init(data,len,path,now); + for(unsigned int f=1;ftotalFragments;++f) + rq->frag0.append(rq->frags[f - 1].payload(),rq->frags[f - 1].payloadLength()); + + if (rq->frag0.tryDecode(RR,tPtr)) { + rq->timestamp = 0; // packet decoded, free entry + } else { + rq->complete = true; // set complete flag but leave entry since it probably needs WHOIS or something + } + } else { + // Still waiting on more fragments, but keep the head + rq->frag0.init(data,len,path,now); + } + } // else this is a duplicate head, ignore + } else { + // Packet is unfragmented, so just process it + IncomingPacket packet(data,len,path,now); + if (!packet.tryDecode(RR,tPtr)) { + Mutex::Lock _l(_rxQueue_m); + RXQueueEntry *rq = &(_rxQueue[ZT_RX_QUEUE_SIZE - 1]); + unsigned long i = ZT_RX_QUEUE_SIZE - 1; + while ((i)&&(rq->timestamp)) { + RXQueueEntry *tmp = &(_rxQueue[--i]); + if (tmp->timestamp < rq->timestamp) + rq = tmp; + } + rq->timestamp = now; + rq->packetId = packet.packetId(); + rq->frag0 = packet; + rq->totalFragments = 1; + rq->haveFragments = 1; + rq->complete = true; + } + } + + // -------------------------------------------------------------------- + } + } + } catch (std::exception &ex) { + TRACE("dropped packet from %s: unexpected exception: %s",fromAddr.toString().c_str(),ex.what()); + } catch ( ... ) { + TRACE("dropped packet from %s: unexpected exception: (unknown)",fromAddr.toString().c_str()); + } +} + +void Switch::onLocalEthernet(void *tPtr,const SharedPtr &network,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) +{ + if (!network->hasConfig()) + return; + + // Check if this packet is from someone other than the tap -- i.e. bridged in + bool fromBridged; + if ((fromBridged = (from != network->mac()))) { + if (!network->config().permitsBridging(RR->identity.address())) { + TRACE("%.16llx: %s -> %s %s not forwarded, bridging disabled or this peer not a bridge",network->id(),from.toString().c_str(),to.toString().c_str(),etherTypeName(etherType)); + return; + } + } + + if (to.isMulticast()) { + MulticastGroup multicastGroup(to,0); + + if (to.isBroadcast()) { + if ( (etherType == ZT_ETHERTYPE_ARP) && (len >= 28) && ((((const uint8_t *)data)[2] == 0x08)&&(((const uint8_t *)data)[3] == 0x00)&&(((const uint8_t *)data)[4] == 6)&&(((const uint8_t *)data)[5] == 4)&&(((const uint8_t *)data)[7] == 0x01)) ) { + /* IPv4 ARP is one of the few special cases that we impose upon what is + * otherwise a straightforward Ethernet switch emulation. Vanilla ARP + * is dumb old broadcast and simply doesn't scale. ZeroTier multicast + * groups have an additional field called ADI (additional distinguishing + * information) which was added specifically for ARP though it could + * be used for other things too. We then take ARP broadcasts and turn + * them into multicasts by stuffing the IP address being queried into + * the 32-bit ADI field. In practice this uses our multicast pub/sub + * system to implement a kind of extended/distributed ARP table. */ + multicastGroup = MulticastGroup::deriveMulticastGroupForAddressResolution(InetAddress(((const unsigned char *)data) + 24,4,0)); + } else if (!network->config().enableBroadcast()) { + // Don't transmit broadcasts if this network doesn't want them + TRACE("%.16llx: dropped broadcast since ff:ff:ff:ff:ff:ff is not enabled",network->id()); + return; + } + } else if ((etherType == ZT_ETHERTYPE_IPV6)&&(len >= (40 + 8 + 16))) { + // IPv6 NDP emulation for certain very special patterns of private IPv6 addresses -- if enabled + if ((network->config().ndpEmulation())&&(reinterpret_cast(data)[6] == 0x3a)&&(reinterpret_cast(data)[40] == 0x87)) { // ICMPv6 neighbor solicitation + Address v6EmbeddedAddress; + const uint8_t *const pkt6 = reinterpret_cast(data) + 40 + 8; + const uint8_t *my6 = (const uint8_t *)0; + + // ZT-RFC4193 address: fdNN:NNNN:NNNN:NNNN:NN99:93DD:DDDD:DDDD / 88 (one /128 per actual host) + + // ZT-6PLANE address: fcXX:XXXX:XXDD:DDDD:DDDD:####:####:#### / 40 (one /80 per actual host) + // (XX - lower 32 bits of network ID XORed with higher 32 bits) + + // For these to work, we must have a ZT-managed address assigned in one of the + // above formats, and the query must match its prefix. + for(unsigned int sipk=0;sipkconfig().staticIpCount;++sipk) { + const InetAddress *const sip = &(network->config().staticIps[sipk]); + if (sip->ss_family == AF_INET6) { + my6 = reinterpret_cast(reinterpret_cast(&(*sip))->sin6_addr.s6_addr); + const unsigned int sipNetmaskBits = Utils::ntoh((uint16_t)reinterpret_cast(&(*sip))->sin6_port); + if ((sipNetmaskBits == 88)&&(my6[0] == 0xfd)&&(my6[9] == 0x99)&&(my6[10] == 0x93)) { // ZT-RFC4193 /88 ??? + unsigned int ptr = 0; + while (ptr != 11) { + if (pkt6[ptr] != my6[ptr]) + break; + ++ptr; + } + if (ptr == 11) { // prefix match! + v6EmbeddedAddress.setTo(pkt6 + ptr,5); + break; + } + } else if (sipNetmaskBits == 40) { // ZT-6PLANE /40 ??? + const uint32_t nwid32 = (uint32_t)((network->id() ^ (network->id() >> 32)) & 0xffffffff); + if ( (my6[0] == 0xfc) && (my6[1] == (uint8_t)((nwid32 >> 24) & 0xff)) && (my6[2] == (uint8_t)((nwid32 >> 16) & 0xff)) && (my6[3] == (uint8_t)((nwid32 >> 8) & 0xff)) && (my6[4] == (uint8_t)(nwid32 & 0xff))) { + unsigned int ptr = 0; + while (ptr != 5) { + if (pkt6[ptr] != my6[ptr]) + break; + ++ptr; + } + if (ptr == 5) { // prefix match! + v6EmbeddedAddress.setTo(pkt6 + ptr,5); + break; + } + } + } + } + } + + if ((v6EmbeddedAddress)&&(v6EmbeddedAddress != RR->identity.address())) { + const MAC peerMac(v6EmbeddedAddress,network->id()); + TRACE("IPv6 NDP emulation: %.16llx: forging response for %s/%s",network->id(),v6EmbeddedAddress.toString().c_str(),peerMac.toString().c_str()); + + uint8_t adv[72]; + adv[0] = 0x60; adv[1] = 0x00; adv[2] = 0x00; adv[3] = 0x00; + adv[4] = 0x00; adv[5] = 0x20; + adv[6] = 0x3a; adv[7] = 0xff; + for(int i=0;i<16;++i) adv[8 + i] = pkt6[i]; + for(int i=0;i<16;++i) adv[24 + i] = my6[i]; + adv[40] = 0x88; adv[41] = 0x00; + adv[42] = 0x00; adv[43] = 0x00; // future home of checksum + adv[44] = 0x60; adv[45] = 0x00; adv[46] = 0x00; adv[47] = 0x00; + for(int i=0;i<16;++i) adv[48 + i] = pkt6[i]; + adv[64] = 0x02; adv[65] = 0x01; + adv[66] = peerMac[0]; adv[67] = peerMac[1]; adv[68] = peerMac[2]; adv[69] = peerMac[3]; adv[70] = peerMac[4]; adv[71] = peerMac[5]; + + uint16_t pseudo_[36]; + uint8_t *const pseudo = reinterpret_cast(pseudo_); + for(int i=0;i<32;++i) pseudo[i] = adv[8 + i]; + pseudo[32] = 0x00; pseudo[33] = 0x00; pseudo[34] = 0x00; pseudo[35] = 0x20; + pseudo[36] = 0x00; pseudo[37] = 0x00; pseudo[38] = 0x00; pseudo[39] = 0x3a; + for(int i=0;i<32;++i) pseudo[40 + i] = adv[40 + i]; + uint32_t checksum = 0; + for(int i=0;i<36;++i) checksum += Utils::hton(pseudo_[i]); + while ((checksum >> 16)) checksum = (checksum & 0xffff) + (checksum >> 16); + checksum = ~checksum; + adv[42] = (checksum >> 8) & 0xff; + adv[43] = checksum & 0xff; + + RR->node->putFrame(tPtr,network->id(),network->userPtr(),peerMac,from,ZT_ETHERTYPE_IPV6,0,adv,72); + return; // NDP emulation done. We have forged a "fake" reply, so no need to send actual NDP query. + } // else no NDP emulation + } // else no NDP emulation + } + + // Check this after NDP emulation, since that has to be allowed in exactly this case + if (network->config().multicastLimit == 0) { + TRACE("%.16llx: dropped multicast: not allowed on network",network->id()); + return; + } + + /* Learn multicast groups for bridged-in hosts. + * Note that some OSes, most notably Linux, do this for you by learning + * multicast addresses on bridge interfaces and subscribing each slave. + * But in that case this does no harm, as the sets are just merged. */ + if (fromBridged) + network->learnBridgedMulticastGroup(tPtr,multicastGroup,RR->node->now()); + + //TRACE("%.16llx: MULTICAST %s -> %s %s %u",network->id(),from.toString().c_str(),multicastGroup.toString().c_str(),etherTypeName(etherType),len); + + // First pass sets noTee to false, but noTee is set to true in OutboundMulticast to prevent duplicates. + if (!network->filterOutgoingPacket(tPtr,false,RR->identity.address(),Address(),from,to,(const uint8_t *)data,len,etherType,vlanId)) { + TRACE("%.16llx: %s -> %s %s packet not sent: filterOutgoingPacket() returned false",network->id(),from.toString().c_str(),to.toString().c_str(),etherTypeName(etherType)); + return; + } + + RR->mc->send( + tPtr, + network->config().multicastLimit, + RR->node->now(), + network->id(), + network->config().disableCompression(), + network->config().activeBridges(), + multicastGroup, + (fromBridged) ? from : MAC(), + etherType, + data, + len); + } else if (to == network->mac()) { + // Destination is this node, so just reinject it + RR->node->putFrame(tPtr,network->id(),network->userPtr(),from,to,etherType,vlanId,data,len); + } else if (to[0] == MAC::firstOctetForNetwork(network->id())) { + // Destination is another ZeroTier peer on the same network + + Address toZT(to.toAddress(network->id())); // since in-network MACs are derived from addresses and network IDs, we can reverse this + SharedPtr toPeer(RR->topology->getPeer(tPtr,toZT)); + + if (!network->filterOutgoingPacket(tPtr,false,RR->identity.address(),toZT,from,to,(const uint8_t *)data,len,etherType,vlanId)) { + TRACE("%.16llx: %s -> %s %s packet not sent: filterOutgoingPacket() returned false",network->id(),from.toString().c_str(),to.toString().c_str(),etherTypeName(etherType)); + return; + } + + if (fromBridged) { + Packet outp(toZT,RR->identity.address(),Packet::VERB_EXT_FRAME); + outp.append(network->id()); + outp.append((unsigned char)0x00); + to.appendTo(outp); + from.appendTo(outp); + outp.append((uint16_t)etherType); + outp.append(data,len); + if (!network->config().disableCompression()) + outp.compress(); + send(tPtr,outp,true); + } else { + Packet outp(toZT,RR->identity.address(),Packet::VERB_FRAME); + outp.append(network->id()); + outp.append((uint16_t)etherType); + outp.append(data,len); + if (!network->config().disableCompression()) + outp.compress(); + send(tPtr,outp,true); + } + + //TRACE("%.16llx: UNICAST: %s -> %s etherType==%s(%.4x) vlanId==%u len==%u fromBridged==%d includeCom==%d",network->id(),from.toString().c_str(),to.toString().c_str(),etherTypeName(etherType),etherType,vlanId,len,(int)fromBridged,(int)includeCom); + } else { + // Destination is bridged behind a remote peer + + // We filter with a NULL destination ZeroTier address first. Filtrations + // for each ZT destination are also done below. This is the same rationale + // and design as for multicast. + if (!network->filterOutgoingPacket(tPtr,false,RR->identity.address(),Address(),from,to,(const uint8_t *)data,len,etherType,vlanId)) { + TRACE("%.16llx: %s -> %s %s packet not sent: filterOutgoingPacket() returned false",network->id(),from.toString().c_str(),to.toString().c_str(),etherTypeName(etherType)); + return; + } + + Address bridges[ZT_MAX_BRIDGE_SPAM]; + unsigned int numBridges = 0; + + /* Create an array of up to ZT_MAX_BRIDGE_SPAM recipients for this bridged frame. */ + bridges[0] = network->findBridgeTo(to); + std::vector
activeBridges(network->config().activeBridges()); + if ((bridges[0])&&(bridges[0] != RR->identity.address())&&(network->config().permitsBridging(bridges[0]))) { + /* We have a known bridge route for this MAC, send it there. */ + ++numBridges; + } else if (!activeBridges.empty()) { + /* If there is no known route, spam to up to ZT_MAX_BRIDGE_SPAM active + * bridges. If someone responds, we'll learn the route. */ + std::vector
::const_iterator ab(activeBridges.begin()); + if (activeBridges.size() <= ZT_MAX_BRIDGE_SPAM) { + // If there are <= ZT_MAX_BRIDGE_SPAM active bridges, spam them all + while (ab != activeBridges.end()) { + bridges[numBridges++] = *ab; + ++ab; + } + } else { + // Otherwise pick a random set of them + while (numBridges < ZT_MAX_BRIDGE_SPAM) { + if (ab == activeBridges.end()) + ab = activeBridges.begin(); + if (((unsigned long)RR->node->prng() % (unsigned long)activeBridges.size()) == 0) { + bridges[numBridges++] = *ab; + ++ab; + } else ++ab; + } + } + } + + for(unsigned int b=0;bfilterOutgoingPacket(tPtr,true,RR->identity.address(),bridges[b],from,to,(const uint8_t *)data,len,etherType,vlanId)) { + Packet outp(bridges[b],RR->identity.address(),Packet::VERB_EXT_FRAME); + outp.append(network->id()); + outp.append((uint8_t)0x00); + to.appendTo(outp); + from.appendTo(outp); + outp.append((uint16_t)etherType); + outp.append(data,len); + if (!network->config().disableCompression()) + outp.compress(); + send(tPtr,outp,true); + } else { + TRACE("%.16llx: %s -> %s %s packet not sent: filterOutgoingPacket() returned false",network->id(),from.toString().c_str(),to.toString().c_str(),etherTypeName(etherType)); + } + } + } +} + +void Switch::send(void *tPtr,Packet &packet,bool encrypt) +{ + if (packet.destination() == RR->identity.address()) { + TRACE("BUG: caught attempt to send() to self, ignored"); + return; + } + + if (!_trySend(tPtr,packet,encrypt)) { + Mutex::Lock _l(_txQueue_m); + _txQueue.push_back(TXQueueEntry(packet.destination(),RR->node->now(),packet,encrypt)); + } +} + +void Switch::requestWhois(void *tPtr,const Address &addr) +{ +#ifdef ZT_TRACE + if (addr == RR->identity.address()) { + fprintf(stderr,"FATAL BUG: Switch::requestWhois() caught attempt to WHOIS self" ZT_EOL_S); + abort(); + } +#endif + + bool inserted = false; + { + Mutex::Lock _l(_outstandingWhoisRequests_m); + WhoisRequest &r = _outstandingWhoisRequests[addr]; + if (r.lastSent) { + r.retries = 0; // reset retry count if entry already existed, but keep waiting and retry again after normal timeout + } else { + r.lastSent = RR->node->now(); + inserted = true; + } + } + if (inserted) + _sendWhoisRequest(tPtr,addr,(const Address *)0,0); +} + +void Switch::doAnythingWaitingForPeer(void *tPtr,const SharedPtr &peer) +{ + { // cancel pending WHOIS since we now know this peer + Mutex::Lock _l(_outstandingWhoisRequests_m); + _outstandingWhoisRequests.erase(peer->address()); + } + + { // finish processing any packets waiting on peer's public key / identity + Mutex::Lock _l(_rxQueue_m); + unsigned long i = ZT_RX_QUEUE_SIZE; + while (i) { + RXQueueEntry *rq = &(_rxQueue[--i]); + if ((rq->timestamp)&&(rq->complete)) { + if (rq->frag0.tryDecode(RR,tPtr)) + rq->timestamp = 0; + } + } + } + + { // finish sending any packets waiting on peer's public key / identity + Mutex::Lock _l(_txQueue_m); + for(std::list< TXQueueEntry >::iterator txi(_txQueue.begin());txi!=_txQueue.end();) { + if (txi->dest == peer->address()) { + if (_trySend(tPtr,txi->packet,txi->encrypt)) + _txQueue.erase(txi++); + else ++txi; + } else ++txi; + } + } +} + +unsigned long Switch::doTimerTasks(void *tPtr,uint64_t now) +{ + unsigned long nextDelay = 0xffffffff; // ceiling delay, caller will cap to minimum + + { // Retry outstanding WHOIS requests + Mutex::Lock _l(_outstandingWhoisRequests_m); + Hashtable< Address,WhoisRequest >::Iterator i(_outstandingWhoisRequests); + Address *a = (Address *)0; + WhoisRequest *r = (WhoisRequest *)0; + while (i.next(a,r)) { + const unsigned long since = (unsigned long)(now - r->lastSent); + if (since >= ZT_WHOIS_RETRY_DELAY) { + if (r->retries >= ZT_MAX_WHOIS_RETRIES) { + TRACE("WHOIS %s timed out",a->toString().c_str()); + _outstandingWhoisRequests.erase(*a); + } else { + r->lastSent = now; + r->peersConsulted[r->retries] = _sendWhoisRequest(tPtr,*a,r->peersConsulted,(r->retries > 1) ? r->retries : 0); + TRACE("WHOIS %s (retry %u)",a->toString().c_str(),r->retries); + ++r->retries; + nextDelay = std::min(nextDelay,(unsigned long)ZT_WHOIS_RETRY_DELAY); + } + } else { + nextDelay = std::min(nextDelay,ZT_WHOIS_RETRY_DELAY - since); + } + } + } + + { // Time out TX queue packets that never got WHOIS lookups or other info. + Mutex::Lock _l(_txQueue_m); + for(std::list< TXQueueEntry >::iterator txi(_txQueue.begin());txi!=_txQueue.end();) { + if (_trySend(tPtr,txi->packet,txi->encrypt)) + _txQueue.erase(txi++); + else if ((now - txi->creationTime) > ZT_TRANSMIT_QUEUE_TIMEOUT) { + TRACE("TX %s -> %s timed out",txi->packet.source().toString().c_str(),txi->packet.destination().toString().c_str()); + _txQueue.erase(txi++); + } else ++txi; + } + } + + { // Remove really old last unite attempt entries to keep table size controlled + Mutex::Lock _l(_lastUniteAttempt_m); + Hashtable< _LastUniteKey,uint64_t >::Iterator i(_lastUniteAttempt); + _LastUniteKey *k = (_LastUniteKey *)0; + uint64_t *v = (uint64_t *)0; + while (i.next(k,v)) { + if ((now - *v) >= (ZT_MIN_UNITE_INTERVAL * 8)) + _lastUniteAttempt.erase(*k); + } + } + + return nextDelay; +} + +bool Switch::_shouldUnite(const uint64_t now,const Address &source,const Address &destination) +{ + Mutex::Lock _l(_lastUniteAttempt_m); + uint64_t &ts = _lastUniteAttempt[_LastUniteKey(source,destination)]; + if ((now - ts) >= ZT_MIN_UNITE_INTERVAL) { + ts = now; + return true; + } + return false; +} + +Address Switch::_sendWhoisRequest(void *tPtr,const Address &addr,const Address *peersAlreadyConsulted,unsigned int numPeersAlreadyConsulted) +{ + SharedPtr upstream(RR->topology->getUpstreamPeer(peersAlreadyConsulted,numPeersAlreadyConsulted,false)); + if (upstream) { + Packet outp(upstream->address(),RR->identity.address(),Packet::VERB_WHOIS); + addr.appendTo(outp); + RR->node->expectReplyTo(outp.packetId()); + send(tPtr,outp,true); + } + return Address(); +} + +bool Switch::_trySend(void *tPtr,Packet &packet,bool encrypt) +{ + SharedPtr viaPath; + const uint64_t now = RR->node->now(); + const Address destination(packet.destination()); + +#ifdef ZT_ENABLE_CLUSTER + uint64_t clusterMostRecentTs = 0; + int clusterMostRecentMemberId = -1; + uint8_t clusterPeerSecret[ZT_PEER_SECRET_KEY_LENGTH]; + if (RR->cluster) + clusterMostRecentMemberId = RR->cluster->checkSendViaCluster(destination,clusterMostRecentTs,clusterPeerSecret); +#endif + + const SharedPtr peer(RR->topology->getPeer(tPtr,destination)); + if (peer) { + /* First get the best path, and if it's dead (and this is not a root) + * we attempt to re-activate that path but this packet will flow + * upstream. If the path comes back alive, it will be used in the future. + * For roots we don't do the alive check since roots are not required + * to send heartbeats "down" and because we have to at least try to + * go somewhere. */ + + viaPath = peer->getBestPath(now,false); + if ( (viaPath) && (!viaPath->alive(now)) && (!RR->topology->isUpstream(peer->identity())) ) { +#ifdef ZT_ENABLE_CLUSTER + if ((clusterMostRecentMemberId < 0)||(viaPath->lastIn() > clusterMostRecentTs)) { +#endif + if ((now - viaPath->lastOut()) > std::max((now - viaPath->lastIn()) * 4,(uint64_t)ZT_PATH_MIN_REACTIVATE_INTERVAL)) { + peer->attemptToContactAt(tPtr,viaPath->localAddress(),viaPath->address(),now,false,viaPath->nextOutgoingCounter()); + viaPath->sent(now); + } +#ifdef ZT_ENABLE_CLUSTER + } +#endif + viaPath.zero(); + } + +#ifdef ZT_ENABLE_CLUSTER + if (clusterMostRecentMemberId >= 0) { + if ((viaPath)&&(viaPath->lastIn() < clusterMostRecentTs)) + viaPath.zero(); + } else if (!viaPath) { +#else + if (!viaPath) { +#endif + peer->tryMemorizedPath(tPtr,now); // periodically attempt memorized or statically defined paths, if any are known + const SharedPtr relay(RR->topology->getUpstreamPeer()); + if ( (!relay) || (!(viaPath = relay->getBestPath(now,false))) ) { + if (!(viaPath = peer->getBestPath(now,true))) + return false; + } +#ifdef ZT_ENABLE_CLUSTER + } +#else + } +#endif + } else { +#ifdef ZT_ENABLE_CLUSTER + if (clusterMostRecentMemberId < 0) { +#else + requestWhois(tPtr,destination); + return false; // if we are not in cluster mode, there is no way we can send without knowing the peer directly +#endif +#ifdef ZT_ENABLE_CLUSTER + } +#endif + } + + unsigned int chunkSize = std::min(packet.size(),(unsigned int)ZT_UDP_DEFAULT_PAYLOAD_MTU); + packet.setFragmented(chunkSize < packet.size()); + +#ifdef ZT_ENABLE_CLUSTER + const uint64_t trustedPathId = (viaPath) ? RR->topology->getOutboundPathTrust(viaPath->address()) : 0; + if (trustedPathId) { + packet.setTrusted(trustedPathId); + } else { + packet.armor((clusterMostRecentMemberId >= 0) ? clusterPeerSecret : peer->key(),encrypt,(viaPath) ? viaPath->nextOutgoingCounter() : 0); + } +#else + const uint64_t trustedPathId = RR->topology->getOutboundPathTrust(viaPath->address()); + if (trustedPathId) { + packet.setTrusted(trustedPathId); + } else { + packet.armor(peer->key(),encrypt,viaPath->nextOutgoingCounter()); + } +#endif + +#ifdef ZT_ENABLE_CLUSTER + if ( ((viaPath)&&(viaPath->send(RR,tPtr,packet.data(),chunkSize,now))) || ((clusterMostRecentMemberId >= 0)&&(RR->cluster->sendViaCluster(clusterMostRecentMemberId,destination,packet.data(),chunkSize))) ) { +#else + if (viaPath->send(RR,tPtr,packet.data(),chunkSize,now)) { +#endif + if (chunkSize < packet.size()) { + // Too big for one packet, fragment the rest + unsigned int fragStart = chunkSize; + unsigned int remaining = packet.size() - chunkSize; + unsigned int fragsRemaining = (remaining / (ZT_UDP_DEFAULT_PAYLOAD_MTU - ZT_PROTO_MIN_FRAGMENT_LENGTH)); + if ((fragsRemaining * (ZT_UDP_DEFAULT_PAYLOAD_MTU - ZT_PROTO_MIN_FRAGMENT_LENGTH)) < remaining) + ++fragsRemaining; + const unsigned int totalFragments = fragsRemaining + 1; + + for(unsigned int fno=1;fnosend(RR,tPtr,frag.data(),frag.size(),now); + else if (clusterMostRecentMemberId >= 0) + RR->cluster->sendViaCluster(clusterMostRecentMemberId,destination,frag.data(),frag.size()); +#else + viaPath->send(RR,tPtr,frag.data(),frag.size(),now); +#endif + fragStart += chunkSize; + remaining -= chunkSize; + } + } + } + + return true; +} + +} // namespace ZeroTier diff --git a/node/Switch.hpp b/node/Switch.hpp new file mode 100644 index 0000000..ff35093 --- /dev/null +++ b/node/Switch.hpp @@ -0,0 +1,227 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_N_SWITCH_HPP +#define ZT_N_SWITCH_HPP + +#include +#include +#include +#include + +#include "Constants.hpp" +#include "Mutex.hpp" +#include "MAC.hpp" +#include "NonCopyable.hpp" +#include "Packet.hpp" +#include "Utils.hpp" +#include "InetAddress.hpp" +#include "Topology.hpp" +#include "Array.hpp" +#include "Network.hpp" +#include "SharedPtr.hpp" +#include "IncomingPacket.hpp" +#include "Hashtable.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; +class Peer; + +/** + * Core of the distributed Ethernet switch and protocol implementation + * + * This class is perhaps a bit misnamed, but it's basically where everything + * meets. Transport-layer ZT packets come in here, as do virtual network + * packets from tap devices, and this sends them where they need to go and + * wraps/unwraps accordingly. It also handles queues and timeouts and such. + */ +class Switch : NonCopyable +{ +public: + Switch(const RuntimeEnvironment *renv); + + /** + * Called when a packet is received from the real network + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param localAddr Local interface address + * @param fromAddr Internet IP address of origin + * @param data Packet data + * @param len Packet length + */ + void onRemotePacket(void *tPtr,const InetAddress &localAddr,const InetAddress &fromAddr,const void *data,unsigned int len); + + /** + * Called when a packet comes from a local Ethernet tap + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param network Which network's TAP did this packet come from? + * @param from Originating MAC address + * @param to Destination MAC address + * @param etherType Ethernet packet type + * @param vlanId VLAN ID or 0 if none + * @param data Ethernet payload + * @param len Frame length + */ + void onLocalEthernet(void *tPtr,const SharedPtr &network,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len); + + /** + * Send a packet to a ZeroTier address (destination in packet) + * + * The packet must be fully composed with source and destination but not + * yet encrypted. If the destination peer is known the packet + * is sent immediately. Otherwise it is queued and a WHOIS is dispatched. + * + * The packet may be compressed. Compression isn't done here. + * + * Needless to say, the packet's source must be this node. Otherwise it + * won't be encrypted right. (This is not used for relaying.) + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param packet Packet to send (buffer may be modified) + * @param encrypt Encrypt packet payload? (always true except for HELLO) + */ + void send(void *tPtr,Packet &packet,bool encrypt); + + /** + * Request WHOIS on a given address + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param addr Address to look up + */ + void requestWhois(void *tPtr,const Address &addr); + + /** + * Run any processes that are waiting for this peer's identity + * + * Called when we learn of a peer's identity from HELLO, OK(WHOIS), etc. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param peer New peer + */ + void doAnythingWaitingForPeer(void *tPtr,const SharedPtr &peer); + + /** + * Perform retries and other periodic timer tasks + * + * This can return a very long delay if there are no pending timer + * tasks. The caller should cap this comparatively vs. other values. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param now Current time + * @return Number of milliseconds until doTimerTasks() should be run again + */ + unsigned long doTimerTasks(void *tPtr,uint64_t now); + +private: + bool _shouldUnite(const uint64_t now,const Address &source,const Address &destination); + Address _sendWhoisRequest(void *tPtr,const Address &addr,const Address *peersAlreadyConsulted,unsigned int numPeersAlreadyConsulted); + bool _trySend(void *tPtr,Packet &packet,bool encrypt); // packet is modified if return is true + + const RuntimeEnvironment *const RR; + uint64_t _lastBeaconResponse; + + // Outstanding WHOIS requests and how many retries they've undergone + struct WhoisRequest + { + WhoisRequest() : lastSent(0),retries(0) {} + uint64_t lastSent; + Address peersConsulted[ZT_MAX_WHOIS_RETRIES]; // by retry + unsigned int retries; // 0..ZT_MAX_WHOIS_RETRIES + }; + Hashtable< Address,WhoisRequest > _outstandingWhoisRequests; + Mutex _outstandingWhoisRequests_m; + + // Packets waiting for WHOIS replies or other decode info or missing fragments + struct RXQueueEntry + { + RXQueueEntry() : timestamp(0) {} + uint64_t timestamp; // 0 if entry is not in use + uint64_t packetId; + IncomingPacket frag0; // head of packet + Packet::Fragment frags[ZT_MAX_PACKET_FRAGMENTS - 1]; // later fragments (if any) + unsigned int totalFragments; // 0 if only frag0 received, waiting for frags + uint32_t haveFragments; // bit mask, LSB to MSB + bool complete; // if true, packet is complete + }; + RXQueueEntry _rxQueue[ZT_RX_QUEUE_SIZE]; + Mutex _rxQueue_m; + + /* Returns the matching or oldest entry. Caller must check timestamp and + * packet ID to determine which. */ + inline RXQueueEntry *_findRXQueueEntry(uint64_t now,uint64_t packetId) + { + RXQueueEntry *rq; + RXQueueEntry *oldest = &(_rxQueue[ZT_RX_QUEUE_SIZE - 1]); + unsigned long i = ZT_RX_QUEUE_SIZE; + while (i) { + rq = &(_rxQueue[--i]); + if ((rq->packetId == packetId)&&(rq->timestamp)) + return rq; + if ((now - rq->timestamp) >= ZT_RX_QUEUE_EXPIRE) + rq->timestamp = 0; + if (rq->timestamp < oldest->timestamp) + oldest = rq; + } + return oldest; + } + + // ZeroTier-layer TX queue entry + struct TXQueueEntry + { + TXQueueEntry() {} + TXQueueEntry(Address d,uint64_t ct,const Packet &p,bool enc) : + dest(d), + creationTime(ct), + packet(p), + encrypt(enc) {} + + Address dest; + uint64_t creationTime; + Packet packet; // unencrypted/unMAC'd packet -- this is done at send time + bool encrypt; + }; + std::list< TXQueueEntry > _txQueue; + Mutex _txQueue_m; + + // Tracks sending of VERB_RENDEZVOUS to relaying peers + struct _LastUniteKey + { + _LastUniteKey() : x(0),y(0) {} + _LastUniteKey(const Address &a1,const Address &a2) + { + if (a1 > a2) { + x = a2.toInt(); + y = a1.toInt(); + } else { + x = a1.toInt(); + y = a2.toInt(); + } + } + inline unsigned long hashCode() const throw() { return ((unsigned long)x ^ (unsigned long)y); } + inline bool operator==(const _LastUniteKey &k) const throw() { return ((x == k.x)&&(y == k.y)); } + uint64_t x,y; + }; + Hashtable< _LastUniteKey,uint64_t > _lastUniteAttempt; // key is always sorted in ascending order, for set-like behavior + Mutex _lastUniteAttempt_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Tag.cpp b/node/Tag.cpp new file mode 100644 index 0000000..3f924da --- /dev/null +++ b/node/Tag.cpp @@ -0,0 +1,46 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include "Tag.hpp" +#include "RuntimeEnvironment.hpp" +#include "Identity.hpp" +#include "Topology.hpp" +#include "Switch.hpp" +#include "Network.hpp" + +namespace ZeroTier { + +int Tag::verify(const RuntimeEnvironment *RR,void *tPtr) const +{ + if ((!_signedBy)||(_signedBy != Network::controllerFor(_networkId))) + return -1; + const Identity id(RR->topology->getIdentity(tPtr,_signedBy)); + if (!id) { + RR->sw->requestWhois(tPtr,_signedBy); + return 1; + } + try { + Buffer<(sizeof(Tag) * 2)> tmp; + this->serialize(tmp,true); + return (id.verify(tmp.data(),tmp.size(),_signature) ? 0 : -1); + } catch ( ... ) { + return -1; + } +} + +} // namespace ZeroTier diff --git a/node/Tag.hpp b/node/Tag.hpp new file mode 100644 index 0000000..1f7f683 --- /dev/null +++ b/node/Tag.hpp @@ -0,0 +1,202 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_TAG_HPP +#define ZT_TAG_HPP + +#include +#include +#include +#include + +#include "Constants.hpp" +#include "Credential.hpp" +#include "C25519.hpp" +#include "Address.hpp" +#include "Identity.hpp" +#include "Buffer.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * A tag that can be associated with members and matched in rules + * + * Capabilities group rules, while tags group members subject to those + * rules. Tag values can be matched in rules, and tags relevant to a + * capability are presented along with it. + * + * E.g. a capability might be "can speak Samba/CIFS within your + * department." This cap might have a rule to allow TCP/137 but + * only if a given tag ID's value matches between two peers. The + * capability is what members can do, while the tag is who they are. + * Different departments might have tags with the same ID but different + * values. + * + * Unlike capabilities tags are signed only by the issuer and are never + * transferrable. + */ +class Tag : public Credential +{ +public: + static inline Credential::Type credentialType() { return Credential::CREDENTIAL_TYPE_TAG; } + + Tag() + { + memset(this,0,sizeof(Tag)); + } + + /** + * @param nwid Network ID + * @param ts Timestamp + * @param issuedTo Address to which this tag was issued + * @param id Tag ID + * @param value Tag value + */ + Tag(const uint64_t nwid,const uint64_t ts,const Address &issuedTo,const uint32_t id,const uint32_t value) : + _id(id), + _value(value), + _networkId(nwid), + _ts(ts), + _issuedTo(issuedTo), + _signedBy() + { + } + + inline uint32_t id() const { return _id; } + inline const uint32_t &value() const { return _value; } + inline uint64_t networkId() const { return _networkId; } + inline uint64_t timestamp() const { return _ts; } + inline const Address &issuedTo() const { return _issuedTo; } + inline const Address &signedBy() const { return _signedBy; } + + /** + * Sign this tag + * + * @param signer Signing identity, must have private key + * @return True if signature was successful + */ + inline bool sign(const Identity &signer) + { + if (signer.hasPrivate()) { + Buffer tmp; + _signedBy = signer.address(); + this->serialize(tmp,true); + _signature = signer.sign(tmp.data(),tmp.size()); + return true; + } + return false; + } + + /** + * Check this tag's signature + * + * @param RR Runtime environment to allow identity lookup for signedBy + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @return 0 == OK, 1 == waiting for WHOIS, -1 == BAD signature or tag + */ + int verify(const RuntimeEnvironment *RR,void *tPtr) const; + + template + inline void serialize(Buffer &b,const bool forSign = false) const + { + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + + b.append(_networkId); + b.append(_ts); + b.append(_id); + b.append(_value); + + _issuedTo.appendTo(b); + _signedBy.appendTo(b); + if (!forSign) { + b.append((uint8_t)1); // 1 == Ed25519 + b.append((uint16_t)ZT_C25519_SIGNATURE_LEN); // length of signature + b.append(_signature.data,ZT_C25519_SIGNATURE_LEN); + } + + b.append((uint16_t)0); // length of additional fields, currently 0 + + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + } + + template + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + unsigned int p = startAt; + + memset(this,0,sizeof(Tag)); + + _networkId = b.template at(p); p += 8; + _ts = b.template at(p); p += 8; + _id = b.template at(p); p += 4; + + _value = b.template at(p); p += 4; + + _issuedTo.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + _signedBy.setTo(b.field(p,ZT_ADDRESS_LENGTH),ZT_ADDRESS_LENGTH); p += ZT_ADDRESS_LENGTH; + if (b[p++] == 1) { + if (b.template at(p) != ZT_C25519_SIGNATURE_LEN) + throw std::runtime_error("invalid signature length"); + p += 2; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + } else { + p += 2 + b.template at(p); + } + + p += 2 + b.template at(p); + if (p > b.size()) + throw std::runtime_error("extended field overflow"); + + return (p - startAt); + } + + // Provides natural sort order by ID + inline bool operator<(const Tag &t) const { return (_id < t._id); } + + inline bool operator==(const Tag &t) const { return (memcmp(this,&t,sizeof(Tag)) == 0); } + inline bool operator!=(const Tag &t) const { return (memcmp(this,&t,sizeof(Tag)) != 0); } + + // For searching sorted arrays or lists of Tags by ID + struct IdComparePredicate + { + inline bool operator()(const Tag &a,const Tag &b) const { return (a.id() < b.id()); } + inline bool operator()(const uint32_t a,const Tag &b) const { return (a < b.id()); } + inline bool operator()(const Tag &a,const uint32_t b) const { return (a.id() < b); } + inline bool operator()(const Tag *a,const Tag *b) const { return (a->id() < b->id()); } + inline bool operator()(const Tag *a,const Tag &b) const { return (a->id() < b.id()); } + inline bool operator()(const Tag &a,const Tag *b) const { return (a.id() < b->id()); } + inline bool operator()(const uint32_t a,const Tag *b) const { return (a < b->id()); } + inline bool operator()(const Tag *a,const uint32_t b) const { return (a->id() < b); } + inline bool operator()(const uint32_t a,const uint32_t b) const { return (a < b); } + }; + +private: + uint32_t _id; + uint32_t _value; + uint64_t _networkId; + uint64_t _ts; + Address _issuedTo; + Address _signedBy; + C25519::Signature _signature; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Topology.cpp b/node/Topology.cpp new file mode 100644 index 0000000..a1d3733 --- /dev/null +++ b/node/Topology.cpp @@ -0,0 +1,475 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include "Constants.hpp" +#include "Topology.hpp" +#include "RuntimeEnvironment.hpp" +#include "Node.hpp" +#include "Network.hpp" +#include "NetworkConfig.hpp" +#include "Buffer.hpp" +#include "Switch.hpp" + +namespace ZeroTier { + +/* + * 2016-01-13 ZeroTier planet definition for the third planet of Sol: + * + * There are two roots, each of which is a cluster spread across multiple + * continents and providers. They are named Alice and Bob after the + * canonical example names used in cryptography. + * + * Alice: + * + * root-alice-ams-01: Amsterdam, Netherlands + * root-alice-joh-01: Johannesburg, South Africa + * root-alice-nyc-01: New York, New York, USA + * root-alice-sao-01: Sao Paolo, Brazil + * root-alice-sfo-01: San Francisco, California, USA + * root-alice-sgp-01: Singapore + * + * Bob: + * + * root-bob-dfw-01: Dallas, Texas, USA + * root-bob-fra-01: Frankfurt, Germany + * root-bob-par-01: Paris, France + * root-bob-syd-01: Sydney, Australia + * root-bob-tok-01: Tokyo, Japan + * root-bob-tor-01: Toronto, Canada + */ +#define ZT_DEFAULT_WORLD_LENGTH 634 +static const unsigned char ZT_DEFAULT_WORLD[ZT_DEFAULT_WORLD_LENGTH] = {0x01,0x00,0x00,0x00,0x00,0x08,0xea,0xc9,0x0a,0x00,0x00,0x01,0x52,0x3c,0x32,0x50,0x1a,0xb8,0xb3,0x88,0xa4,0x69,0x22,0x14,0x91,0xaa,0x9a,0xcd,0x66,0xcc,0x76,0x4c,0xde,0xfd,0x56,0x03,0x9f,0x10,0x67,0xae,0x15,0xe6,0x9c,0x6f,0xb4,0x2d,0x7b,0x55,0x33,0x0e,0x3f,0xda,0xac,0x52,0x9c,0x07,0x92,0xfd,0x73,0x40,0xa6,0xaa,0x21,0xab,0xa8,0xa4,0x89,0xfd,0xae,0xa4,0x4a,0x39,0xbf,0x2d,0x00,0x65,0x9a,0xc9,0xc8,0x18,0xeb,0x4a,0xf7,0x86,0xa8,0x40,0xd6,0x52,0xea,0xae,0x9e,0x7a,0xbf,0x4c,0x97,0x66,0xab,0x2d,0x6f,0xaf,0xc9,0x2b,0x3a,0xff,0xed,0xd6,0x30,0x3e,0xc4,0x6a,0x65,0xf2,0xbd,0x83,0x52,0xf5,0x40,0xe9,0xcc,0x0d,0x6e,0x89,0x3f,0x9a,0xa0,0xb8,0xdf,0x42,0xd2,0x2f,0x84,0xe6,0x03,0x26,0x0f,0xa8,0xe3,0xcc,0x05,0x05,0x03,0xef,0x12,0x80,0x0d,0xce,0x3e,0xb6,0x58,0x3b,0x1f,0xa8,0xad,0xc7,0x25,0xf9,0x43,0x71,0xa7,0x5c,0x9a,0xc7,0xe1,0xa3,0xb8,0x88,0xd0,0x71,0x6c,0x94,0x99,0x73,0x41,0x0b,0x1b,0x48,0x84,0x02,0x9d,0x21,0x90,0x39,0xf3,0x00,0x01,0xf0,0x92,0x2a,0x98,0xe3,0xb3,0x4e,0xbc,0xbf,0xf3,0x33,0x26,0x9d,0xc2,0x65,0xd7,0xa0,0x20,0xaa,0xb6,0x9d,0x72,0xbe,0x4d,0x4a,0xcc,0x9c,0x8c,0x92,0x94,0x78,0x57,0x71,0x25,0x6c,0xd1,0xd9,0x42,0xa9,0x0d,0x1b,0xd1,0xd2,0xdc,0xa3,0xea,0x84,0xef,0x7d,0x85,0xaf,0xe6,0x61,0x1f,0xb4,0x3f,0xf0,0xb7,0x41,0x26,0xd9,0x0a,0x6e,0x00,0x0c,0x04,0xbc,0xa6,0x5e,0xb1,0x27,0x09,0x06,0x2a,0x03,0xb0,0xc0,0x00,0x02,0x00,0xd0,0x00,0x00,0x00,0x00,0x00,0x7d,0x00,0x01,0x27,0x09,0x04,0x9a,0x42,0xc5,0x21,0x27,0x09,0x06,0x2c,0x0f,0xf8,0x50,0x01,0x54,0x01,0x97,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x27,0x09,0x04,0x9f,0xcb,0x61,0xab,0x27,0x09,0x06,0x26,0x04,0xa8,0x80,0x08,0x00,0x00,0xa1,0x00,0x00,0x00,0x00,0x00,0x54,0x60,0x01,0x27,0x09,0x04,0xa9,0x39,0x8f,0x68,0x27,0x09,0x06,0x26,0x07,0xf0,0xd0,0x1d,0x01,0x00,0x57,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x27,0x09,0x04,0x6b,0xaa,0xc5,0x0e,0x27,0x09,0x06,0x26,0x04,0xa8,0x80,0x00,0x01,0x00,0x20,0x00,0x00,0x00,0x00,0x02,0x00,0xe0,0x01,0x27,0x09,0x04,0x80,0xc7,0xc5,0xd9,0x27,0x09,0x06,0x24,0x00,0x61,0x80,0x00,0x00,0x00,0xd0,0x00,0x00,0x00,0x00,0x00,0xb7,0x40,0x01,0x27,0x09,0x88,0x41,0x40,0x8a,0x2e,0x00,0xbb,0x1d,0x31,0xf2,0xc3,0x23,0xe2,0x64,0xe9,0xe6,0x41,0x72,0xc1,0xa7,0x4f,0x77,0x89,0x95,0x55,0xed,0x10,0x75,0x1c,0xd5,0x6e,0x86,0x40,0x5c,0xde,0x11,0x8d,0x02,0xdf,0xfe,0x55,0x5d,0x46,0x2c,0xcf,0x6a,0x85,0xb5,0x63,0x1c,0x12,0x35,0x0c,0x8d,0x5d,0xc4,0x09,0xba,0x10,0xb9,0x02,0x5d,0x0f,0x44,0x5c,0xf4,0x49,0xd9,0x2b,0x1c,0x00,0x0c,0x04,0x2d,0x20,0xc6,0x82,0x27,0x09,0x06,0x20,0x01,0x19,0xf0,0x64,0x00,0x81,0xc3,0x54,0x00,0x00,0xff,0xfe,0x18,0x1d,0x61,0x27,0x09,0x04,0x2e,0x65,0xa0,0xf9,0x27,0x09,0x06,0x2a,0x03,0xb0,0xc0,0x00,0x03,0x00,0xd0,0x00,0x00,0x00,0x00,0x00,0x6a,0x30,0x01,0x27,0x09,0x04,0x6b,0xbf,0x2e,0xd2,0x27,0x09,0x06,0x20,0x01,0x19,0xf0,0x68,0x00,0x83,0xa4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x64,0x27,0x09,0x04,0x2d,0x20,0xf6,0xb3,0x27,0x09,0x06,0x20,0x01,0x19,0xf0,0x58,0x00,0x8b,0xf8,0x54,0x00,0x00,0xff,0xfe,0x15,0xb3,0x9a,0x27,0x09,0x04,0x2d,0x20,0xf8,0x57,0x27,0x09,0x06,0x20,0x01,0x19,0xf0,0x70,0x00,0x9b,0xc9,0x54,0x00,0x00,0xff,0xfe,0x15,0xc4,0xf5,0x27,0x09,0x04,0x9f,0xcb,0x02,0x9a,0x27,0x09,0x06,0x26,0x04,0xa8,0x80,0x0c,0xad,0x00,0xd0,0x00,0x00,0x00,0x00,0x00,0x26,0x70,0x01,0x27,0x09}; + +Topology::Topology(const RuntimeEnvironment *renv,void *tPtr) : + RR(renv), + _trustedPathCount(0), + _amRoot(false) +{ + try { + World cachedPlanet; + std::string buf(RR->node->dataStoreGet(tPtr,"planet")); + if (buf.length() > 0) { + Buffer dswtmp(buf.data(),(unsigned int)buf.length()); + cachedPlanet.deserialize(dswtmp,0); + } + addWorld(tPtr,cachedPlanet,false); + } catch ( ... ) {} + + World defaultPlanet; + { + Buffer wtmp(ZT_DEFAULT_WORLD,ZT_DEFAULT_WORLD_LENGTH); + defaultPlanet.deserialize(wtmp,0); // throws on error, which would indicate a bad static variable up top + } + addWorld(tPtr,defaultPlanet,false); +} + +SharedPtr Topology::addPeer(void *tPtr,const SharedPtr &peer) +{ +#ifdef ZT_TRACE + if ((!peer)||(peer->address() == RR->identity.address())) { + if (!peer) + fprintf(stderr,"FATAL BUG: addPeer() caught attempt to add NULL peer" ZT_EOL_S); + else fprintf(stderr,"FATAL BUG: addPeer() caught attempt to add peer for self" ZT_EOL_S); + abort(); + } +#endif + + SharedPtr np; + { + Mutex::Lock _l(_peers_m); + SharedPtr &hp = _peers[peer->address()]; + if (!hp) + hp = peer; + np = hp; + } + + saveIdentity(tPtr,np->identity()); + + return np; +} + +SharedPtr Topology::getPeer(void *tPtr,const Address &zta) +{ + if (zta == RR->identity.address()) { + TRACE("BUG: ignored attempt to getPeer() for self, returned NULL"); + return SharedPtr(); + } + + { + Mutex::Lock _l(_peers_m); + const SharedPtr *const ap = _peers.get(zta); + if (ap) + return *ap; + } + + try { + Identity id(_getIdentity(tPtr,zta)); + if (id) { + SharedPtr np(new Peer(RR,RR->identity,id)); + { + Mutex::Lock _l(_peers_m); + SharedPtr &ap = _peers[zta]; + if (!ap) + ap.swap(np); + return ap; + } + } + } catch ( ... ) {} // invalid identity on disk? + + return SharedPtr(); +} + +Identity Topology::getIdentity(void *tPtr,const Address &zta) +{ + if (zta == RR->identity.address()) { + return RR->identity; + } else { + Mutex::Lock _l(_peers_m); + const SharedPtr *const ap = _peers.get(zta); + if (ap) + return (*ap)->identity(); + } + return _getIdentity(tPtr,zta); +} + +void Topology::saveIdentity(void *tPtr,const Identity &id) +{ + if (id) { + char p[128]; + Utils::snprintf(p,sizeof(p),"iddb.d/%.10llx",(unsigned long long)id.address().toInt()); + RR->node->dataStorePut(tPtr,p,id.toString(false),false); + } +} + +SharedPtr Topology::getUpstreamPeer(const Address *avoid,unsigned int avoidCount,bool strictAvoid) +{ + const uint64_t now = RR->node->now(); + unsigned int bestQualityOverall = ~((unsigned int)0); + unsigned int bestQualityNotAvoid = ~((unsigned int)0); + const SharedPtr *bestOverall = (const SharedPtr *)0; + const SharedPtr *bestNotAvoid = (const SharedPtr *)0; + + Mutex::Lock _l1(_peers_m); + Mutex::Lock _l2(_upstreams_m); + + for(std::vector
::const_iterator a(_upstreamAddresses.begin());a!=_upstreamAddresses.end();++a) { + const SharedPtr *p = _peers.get(*a); + if (p) { + bool avoiding = false; + for(unsigned int i=0;iaddress()) { + avoiding = true; + break; + } + } + const unsigned int q = (*p)->relayQuality(now); + if (q <= bestQualityOverall) { + bestQualityOverall = q; + bestOverall = &(*p); + } + if ((!avoiding)&&(q <= bestQualityNotAvoid)) { + bestQualityNotAvoid = q; + bestNotAvoid = &(*p); + } + } + } + + if (bestNotAvoid) { + return *bestNotAvoid; + } else if ((!strictAvoid)&&(bestOverall)) { + return *bestOverall; + } + + return SharedPtr(); +} + +bool Topology::isUpstream(const Identity &id) const +{ + Mutex::Lock _l(_upstreams_m); + return (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),id.address()) != _upstreamAddresses.end()); +} + +bool Topology::shouldAcceptWorldUpdateFrom(const Address &addr) const +{ + Mutex::Lock _l(_upstreams_m); + if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),addr) != _upstreamAddresses.end()) + return true; + for(std::vector< std::pair< uint64_t,Address> >::const_iterator s(_moonSeeds.begin());s!=_moonSeeds.end();++s) { + if (s->second == addr) + return true; + } + return false; +} + +ZT_PeerRole Topology::role(const Address &ztaddr) const +{ + Mutex::Lock _l(_upstreams_m); + if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),ztaddr) != _upstreamAddresses.end()) { + for(std::vector::const_iterator i(_planet.roots().begin());i!=_planet.roots().end();++i) { + if (i->identity.address() == ztaddr) + return ZT_PEER_ROLE_PLANET; + } + return ZT_PEER_ROLE_MOON; + } + return ZT_PEER_ROLE_LEAF; +} + +bool Topology::isProhibitedEndpoint(const Address &ztaddr,const InetAddress &ipaddr) const +{ + Mutex::Lock _l(_upstreams_m); + + // For roots the only permitted addresses are those defined. This adds just a little + // bit of extra security against spoofing, replaying, etc. + if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),ztaddr) != _upstreamAddresses.end()) { + for(std::vector::const_iterator r(_planet.roots().begin());r!=_planet.roots().end();++r) { + if (r->identity.address() == ztaddr) { + if (r->stableEndpoints.size() == 0) + return false; // no stable endpoints specified, so allow dynamic paths + for(std::vector::const_iterator e(r->stableEndpoints.begin());e!=r->stableEndpoints.end();++e) { + if (ipaddr.ipsEqual(*e)) + return false; + } + } + } + for(std::vector::const_iterator m(_moons.begin());m!=_moons.end();++m) { + for(std::vector::const_iterator r(m->roots().begin());r!=m->roots().end();++r) { + if (r->identity.address() == ztaddr) { + if (r->stableEndpoints.size() == 0) + return false; // no stable endpoints specified, so allow dynamic paths + for(std::vector::const_iterator e(r->stableEndpoints.begin());e!=r->stableEndpoints.end();++e) { + if (ipaddr.ipsEqual(*e)) + return false; + } + } + } + } + return true; + } + + return false; +} + +bool Topology::addWorld(void *tPtr,const World &newWorld,bool alwaysAcceptNew) +{ + if ((newWorld.type() != World::TYPE_PLANET)&&(newWorld.type() != World::TYPE_MOON)) + return false; + + Mutex::Lock _l1(_upstreams_m); + Mutex::Lock _l2(_peers_m); + + World *existing = (World *)0; + switch(newWorld.type()) { + case World::TYPE_PLANET: + existing = &_planet; + break; + case World::TYPE_MOON: + for(std::vector< World >::iterator m(_moons.begin());m!=_moons.end();++m) { + if (m->id() == newWorld.id()) { + existing = &(*m); + break; + } + } + break; + default: + return false; + } + + if (existing) { + if (existing->shouldBeReplacedBy(newWorld)) + *existing = newWorld; + else return false; + } else if (newWorld.type() == World::TYPE_MOON) { + if (alwaysAcceptNew) { + _moons.push_back(newWorld); + existing = &(_moons.back()); + } else { + for(std::vector< std::pair >::iterator m(_moonSeeds.begin());m!=_moonSeeds.end();++m) { + if (m->first == newWorld.id()) { + for(std::vector::const_iterator r(newWorld.roots().begin());r!=newWorld.roots().end();++r) { + if (r->identity.address() == m->second) { + _moonSeeds.erase(m); + _moons.push_back(newWorld); + existing = &(_moons.back()); + break; + } + } + if (existing) + break; + } + } + } + if (!existing) + return false; + } else { + return false; + } + + char savePath[64]; + if (existing->type() == World::TYPE_MOON) { + Utils::snprintf(savePath,sizeof(savePath),"moons.d/%.16llx.moon",existing->id()); + } else { + Utils::scopy(savePath,sizeof(savePath),"planet"); + } + try { + Buffer dswtmp; + existing->serialize(dswtmp,false); + RR->node->dataStorePut(tPtr,savePath,dswtmp.data(),dswtmp.size(),false); + } catch ( ... ) { + RR->node->dataStoreDelete(tPtr,savePath); + } + + _memoizeUpstreams(tPtr); + + return true; +} + +void Topology::addMoon(void *tPtr,const uint64_t id,const Address &seed) +{ + char savePath[64]; + Utils::snprintf(savePath,sizeof(savePath),"moons.d/%.16llx.moon",id); + + try { + std::string moonBin(RR->node->dataStoreGet(tPtr,savePath)); + if (moonBin.length() > 1) { + Buffer wtmp(moonBin.data(),(unsigned int)moonBin.length()); + World w; + w.deserialize(wtmp); + if ((w.type() == World::TYPE_MOON)&&(w.id() == id)) { + addWorld(tPtr,w,true); + return; + } + } + } catch ( ... ) {} + + if (seed) { + Mutex::Lock _l(_upstreams_m); + if (std::find(_moonSeeds.begin(),_moonSeeds.end(),std::pair(id,seed)) == _moonSeeds.end()) + _moonSeeds.push_back(std::pair(id,seed)); + } +} + +void Topology::removeMoon(void *tPtr,const uint64_t id) +{ + Mutex::Lock _l1(_upstreams_m); + Mutex::Lock _l2(_peers_m); + + std::vector nm; + for(std::vector::const_iterator m(_moons.begin());m!=_moons.end();++m) { + if (m->id() != id) { + nm.push_back(*m); + } else { + char savePath[64]; + Utils::snprintf(savePath,sizeof(savePath),"moons.d/%.16llx.moon",id); + RR->node->dataStoreDelete(tPtr,savePath); + } + } + _moons.swap(nm); + + std::vector< std::pair > cm; + for(std::vector< std::pair >::const_iterator m(_moonSeeds.begin());m!=_moonSeeds.end();++m) { + if (m->first != id) + cm.push_back(*m); + } + _moonSeeds.swap(cm); + + _memoizeUpstreams(tPtr); +} + +void Topology::clean(uint64_t now) +{ + { + Mutex::Lock _l1(_peers_m); + Mutex::Lock _l2(_upstreams_m); + Hashtable< Address,SharedPtr >::Iterator i(_peers); + Address *a = (Address *)0; + SharedPtr *p = (SharedPtr *)0; + while (i.next(a,p)) { + if ( (!(*p)->isAlive(now)) && (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),*a) == _upstreamAddresses.end()) ) + _peers.erase(*a); + } + } + { + Mutex::Lock _l(_paths_m); + Hashtable< Path::HashKey,SharedPtr >::Iterator i(_paths); + Path::HashKey *k = (Path::HashKey *)0; + SharedPtr *p = (SharedPtr *)0; + while (i.next(k,p)) { + if (p->reclaimIfWeak()) + _paths.erase(*k); + } + } +} + +Identity Topology::_getIdentity(void *tPtr,const Address &zta) +{ + char p[128]; + Utils::snprintf(p,sizeof(p),"iddb.d/%.10llx",(unsigned long long)zta.toInt()); + std::string ids(RR->node->dataStoreGet(tPtr,p)); + if (ids.length() > 0) { + try { + return Identity(ids); + } catch ( ... ) {} // ignore invalid IDs + } + return Identity(); +} + +void Topology::_memoizeUpstreams(void *tPtr) +{ + // assumes _upstreams_m and _peers_m are locked + _upstreamAddresses.clear(); + _amRoot = false; + + for(std::vector::const_iterator i(_planet.roots().begin());i!=_planet.roots().end();++i) { + if (i->identity == RR->identity) { + _amRoot = true; + } else if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),i->identity.address()) == _upstreamAddresses.end()) { + _upstreamAddresses.push_back(i->identity.address()); + SharedPtr &hp = _peers[i->identity.address()]; + if (!hp) { + hp = new Peer(RR,RR->identity,i->identity); + saveIdentity(tPtr,i->identity); + } + } + } + + for(std::vector::const_iterator m(_moons.begin());m!=_moons.end();++m) { + for(std::vector::const_iterator i(m->roots().begin());i!=m->roots().end();++i) { + if (i->identity == RR->identity) { + _amRoot = true; + } else if (std::find(_upstreamAddresses.begin(),_upstreamAddresses.end(),i->identity.address()) == _upstreamAddresses.end()) { + _upstreamAddresses.push_back(i->identity.address()); + SharedPtr &hp = _peers[i->identity.address()]; + if (!hp) { + hp = new Peer(RR,RR->identity,i->identity); + saveIdentity(tPtr,i->identity); + } + } + } + } + + std::sort(_upstreamAddresses.begin(),_upstreamAddresses.end()); + + _cor.clear(); + for(std::vector
::const_iterator a(_upstreamAddresses.begin());a!=_upstreamAddresses.end();++a) { + if (!_cor.addRepresentative(*a)) + break; + } + _cor.sign(RR->identity,RR->node->now()); +} + +} // namespace ZeroTier diff --git a/node/Topology.hpp b/node/Topology.hpp new file mode 100644 index 0000000..d29c424 --- /dev/null +++ b/node/Topology.hpp @@ -0,0 +1,458 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_TOPOLOGY_HPP +#define ZT_TOPOLOGY_HPP + +#include +#include + +#include +#include +#include +#include + +#include "Constants.hpp" +#include "../include/ZeroTierOne.h" + +#include "Address.hpp" +#include "Identity.hpp" +#include "Peer.hpp" +#include "Path.hpp" +#include "Mutex.hpp" +#include "InetAddress.hpp" +#include "Hashtable.hpp" +#include "World.hpp" +#include "CertificateOfRepresentation.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Database of network topology + */ +class Topology +{ +public: + Topology(const RuntimeEnvironment *renv,void *tPtr); + + /** + * Add a peer to database + * + * This will not replace existing peers. In that case the existing peer + * record is returned. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param peer Peer to add + * @return New or existing peer (should replace 'peer') + */ + SharedPtr addPeer(void *tPtr,const SharedPtr &peer); + + /** + * Get a peer from its address + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param zta ZeroTier address of peer + * @return Peer or NULL if not found + */ + SharedPtr getPeer(void *tPtr,const Address &zta); + + /** + * Get a peer only if it is presently in memory (no disk cache) + * + * This also does not update the lastUsed() time for peers, which means + * that it won't prevent them from falling out of RAM. This is currently + * used in the Cluster code to update peer info without forcing all peers + * across the entire cluster to remain in memory cache. + * + * @param zta ZeroTier address + */ + inline SharedPtr getPeerNoCache(const Address &zta) + { + Mutex::Lock _l(_peers_m); + const SharedPtr *const ap = _peers.get(zta); + if (ap) + return *ap; + return SharedPtr(); + } + + /** + * Get a Path object for a given local and remote physical address, creating if needed + * + * @param l Local address or NULL for 'any' or 'wildcard' + * @param r Remote address + * @return Pointer to canonicalized Path object + */ + inline SharedPtr getPath(const InetAddress &l,const InetAddress &r) + { + Mutex::Lock _l(_paths_m); + SharedPtr &p = _paths[Path::HashKey(l,r)]; + if (!p) + p.setToUnsafe(new Path(l,r)); + return p; + } + + /** + * Get the identity of a peer + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param zta ZeroTier address of peer + * @return Identity or NULL Identity if not found + */ + Identity getIdentity(void *tPtr,const Address &zta); + + /** + * Cache an identity + * + * This is done automatically on addPeer(), and so is only useful for + * cluster identity replication. + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param id Identity to cache + */ + void saveIdentity(void *tPtr,const Identity &id); + + /** + * Get the current best upstream peer + * + * @return Root server with lowest latency or NULL if none + */ + inline SharedPtr getUpstreamPeer() { return getUpstreamPeer((const Address *)0,0,false); } + + /** + * Get the current best upstream peer, avoiding those in the supplied avoid list + * + * @param avoid Nodes to avoid + * @param avoidCount Number of nodes to avoid + * @param strictAvoid If false, consider avoided root servers anyway if no non-avoid root servers are available + * @return Root server or NULL if none available + */ + SharedPtr getUpstreamPeer(const Address *avoid,unsigned int avoidCount,bool strictAvoid); + + /** + * @param id Identity to check + * @return True if this is a root server or a network preferred relay from one of our networks + */ + bool isUpstream(const Identity &id) const; + + /** + * @param addr Address to check + * @return True if we should accept a world update from this address + */ + bool shouldAcceptWorldUpdateFrom(const Address &addr) const; + + /** + * @param ztaddr ZeroTier address + * @return Peer role for this device + */ + ZT_PeerRole role(const Address &ztaddr) const; + + /** + * Check for prohibited endpoints + * + * Right now this returns true if the designated ZT address is a root and if + * the IP (IP only, not port) does not equal any of the IPs defined in the + * current World. This is an extra little security feature in case root keys + * get appropriated or something. + * + * Otherwise it returns false. + * + * @param ztaddr ZeroTier address + * @param ipaddr IP address + * @return True if this ZT/IP pair should not be allowed to be used + */ + bool isProhibitedEndpoint(const Address &ztaddr,const InetAddress &ipaddr) const; + + /** + * Gets upstreams to contact and their stable endpoints (if known) + * + * @param eps Hash table to fill with addresses and their stable endpoints + */ + inline void getUpstreamsToContact(Hashtable< Address,std::vector > &eps) const + { + Mutex::Lock _l(_upstreams_m); + for(std::vector::const_iterator i(_planet.roots().begin());i!=_planet.roots().end();++i) { + if (i->identity != RR->identity) { + std::vector &ips = eps[i->identity.address()]; + for(std::vector::const_iterator j(i->stableEndpoints.begin());j!=i->stableEndpoints.end();++j) { + if (std::find(ips.begin(),ips.end(),*j) == ips.end()) + ips.push_back(*j); + } + } + } + for(std::vector::const_iterator m(_moons.begin());m!=_moons.end();++m) { + for(std::vector::const_iterator i(m->roots().begin());i!=m->roots().end();++i) { + if (i->identity != RR->identity) { + std::vector &ips = eps[i->identity.address()]; + for(std::vector::const_iterator j(i->stableEndpoints.begin());j!=i->stableEndpoints.end();++j) { + if (std::find(ips.begin(),ips.end(),*j) == ips.end()) + ips.push_back(*j); + } + } + } + } + for(std::vector< std::pair >::const_iterator m(_moonSeeds.begin());m!=_moonSeeds.end();++m) + eps[m->second]; + } + + /** + * @return Vector of active upstream addresses (including roots) + */ + inline std::vector
upstreamAddresses() const + { + Mutex::Lock _l(_upstreams_m); + return _upstreamAddresses; + } + + /** + * @return Current moons + */ + inline std::vector moons() const + { + Mutex::Lock _l(_upstreams_m); + return _moons; + } + + /** + * @return Moon IDs we are waiting for from seeds + */ + inline std::vector moonsWanted() const + { + Mutex::Lock _l(_upstreams_m); + std::vector mw; + for(std::vector< std::pair >::const_iterator s(_moonSeeds.begin());s!=_moonSeeds.end();++s) { + if (std::find(mw.begin(),mw.end(),s->first) == mw.end()) + mw.push_back(s->first); + } + return mw; + } + + /** + * @return Current planet + */ + inline World planet() const + { + Mutex::Lock _l(_upstreams_m); + return _planet; + } + + /** + * @return Current planet's world ID + */ + inline uint64_t planetWorldId() const + { + return _planet.id(); // safe to read without lock, and used from within eachPeer() so don't lock + } + + /** + * @return Current planet's world timestamp + */ + inline uint64_t planetWorldTimestamp() const + { + return _planet.timestamp(); // safe to read without lock, and used from within eachPeer() so don't lock + } + + /** + * Validate new world and update if newer and signature is okay + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param newWorld A new or updated planet or moon to learn + * @param alwaysAcceptNew If true, always accept new moons even if we're not waiting for one + * @return True if it was valid and newer than current (or totally new for moons) + */ + bool addWorld(void *tPtr,const World &newWorld,bool alwaysAcceptNew); + + /** + * Add a moon + * + * This loads it from moons.d if present, and if not adds it to + * a list of moons that we want to contact. + * + * @param id Moon ID + * @param seed If non-NULL, an address of any member of the moon to contact + */ + void addMoon(void *tPtr,const uint64_t id,const Address &seed); + + /** + * Remove a moon + * + * @param tPtr Thread pointer to be handed through to any callbacks called as a result of this call + * @param id Moon's world ID + */ + void removeMoon(void *tPtr,const uint64_t id); + + /** + * Clean and flush database + */ + void clean(uint64_t now); + + /** + * @param now Current time + * @return Number of peers with active direct paths + */ + inline unsigned long countActive(uint64_t now) const + { + unsigned long cnt = 0; + Mutex::Lock _l(_peers_m); + Hashtable< Address,SharedPtr >::Iterator i(const_cast(this)->_peers); + Address *a = (Address *)0; + SharedPtr *p = (SharedPtr *)0; + while (i.next(a,p)) { + const SharedPtr pp((*p)->getBestPath(now,false)); + if ((pp)&&(pp->alive(now))) + ++cnt; + } + return cnt; + } + + /** + * Apply a function or function object to all peers + * + * @param f Function to apply + * @tparam F Function or function object type + */ + template + inline void eachPeer(F f) + { + Mutex::Lock _l(_peers_m); + Hashtable< Address,SharedPtr >::Iterator i(_peers); + Address *a = (Address *)0; + SharedPtr *p = (SharedPtr *)0; + while (i.next(a,p)) { +#ifdef ZT_TRACE + if (!(*p)) { + fprintf(stderr,"FATAL BUG: eachPeer() caught NULL peer for %s -- peer pointers in Topology should NEVER be NULL" ZT_EOL_S,a->toString().c_str()); + abort(); + } +#endif + f(*this,*((const SharedPtr *)p)); + } + } + + /** + * @return All currently active peers by address (unsorted) + */ + inline std::vector< std::pair< Address,SharedPtr > > allPeers() const + { + Mutex::Lock _l(_peers_m); + return _peers.entries(); + } + + /** + * @return True if I am a root server in a planet or moon + */ + inline bool amRoot() const { return _amRoot; } + + /** + * Get the outbound trusted path ID for a physical address, or 0 if none + * + * @param physicalAddress Physical address to which we are sending the packet + * @return Trusted path ID or 0 if none (0 is not a valid trusted path ID) + */ + inline uint64_t getOutboundPathTrust(const InetAddress &physicalAddress) + { + for(unsigned int i=0;i<_trustedPathCount;++i) { + if (_trustedPathNetworks[i].containsAddress(physicalAddress)) + return _trustedPathIds[i]; + } + return 0; + } + + /** + * Check whether in incoming trusted path marked packet is valid + * + * @param physicalAddress Originating physical address + * @param trustedPathId Trusted path ID from packet (from MAC field) + */ + inline bool shouldInboundPathBeTrusted(const InetAddress &physicalAddress,const uint64_t trustedPathId) + { + for(unsigned int i=0;i<_trustedPathCount;++i) { + if ((_trustedPathIds[i] == trustedPathId)&&(_trustedPathNetworks[i].containsAddress(physicalAddress))) + return true; + } + return false; + } + + /** + * Set trusted paths in this topology + * + * @param networks Array of networks (prefix/netmask bits) + * @param ids Array of trusted path IDs + * @param count Number of trusted paths (if larger than ZT_MAX_TRUSTED_PATHS overflow is ignored) + */ + inline void setTrustedPaths(const InetAddress *networks,const uint64_t *ids,unsigned int count) + { + if (count > ZT_MAX_TRUSTED_PATHS) + count = ZT_MAX_TRUSTED_PATHS; + Mutex::Lock _l(_trustedPaths_m); + for(unsigned int i=0;i + void appendCertificateOfRepresentation(Buffer &buf) + { + Mutex::Lock _l(_upstreams_m); + _cor.serialize(buf); + } + +private: + Identity _getIdentity(void *tPtr,const Address &zta); + void _memoizeUpstreams(void *tPtr); + + const RuntimeEnvironment *const RR; + + uint64_t _trustedPathIds[ZT_MAX_TRUSTED_PATHS]; + InetAddress _trustedPathNetworks[ZT_MAX_TRUSTED_PATHS]; + unsigned int _trustedPathCount; + Mutex _trustedPaths_m; + + Hashtable< Address,SharedPtr > _peers; + Mutex _peers_m; + + Hashtable< Path::HashKey,SharedPtr > _paths; + Mutex _paths_m; + + World _planet; + std::vector _moons; + std::vector< std::pair > _moonSeeds; + std::vector
_upstreamAddresses; + CertificateOfRepresentation _cor; + bool _amRoot; + Mutex _upstreams_m; // locks worlds, upstream info, moon info, etc. +}; + +} // namespace ZeroTier + +#endif diff --git a/node/Utils.cpp b/node/Utils.cpp new file mode 100644 index 0000000..9ce1bf0 --- /dev/null +++ b/node/Utils.cpp @@ -0,0 +1,257 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include "Constants.hpp" + +#ifdef __UNIX_LIKE__ +#include +#include +#include +#include +#include +#include +#include +#endif + +#ifdef __WINDOWS__ +#include +#endif + +#include "Utils.hpp" +#include "Mutex.hpp" +#include "Salsa20.hpp" + +namespace ZeroTier { + +const char Utils::HEXCHARS[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' }; + +// Crazy hack to force memory to be securely zeroed in spite of the best efforts of optimizing compilers. +static void _Utils_doBurn(volatile uint8_t *ptr,unsigned int len) +{ + volatile uint8_t *const end = ptr + len; + while (ptr != end) *(ptr++) = (uint8_t)0; +} +static void (*volatile _Utils_doBurn_ptr)(volatile uint8_t *,unsigned int) = _Utils_doBurn; +void Utils::burn(void *ptr,unsigned int len) { (_Utils_doBurn_ptr)((volatile uint8_t *)ptr,len); } + +std::string Utils::hex(const void *data,unsigned int len) +{ + std::string r; + r.reserve(len * 2); + for(unsigned int i=0;i> 4]); + r.push_back(HEXCHARS[((const unsigned char *)data)[i] & 0x0f]); + } + return r; +} + +std::string Utils::unhex(const char *hex,unsigned int maxlen) +{ + int n = 1; + unsigned char c,b = 0; + const char *eof = hex + maxlen; + std::string r; + + if (!maxlen) + return r; + + while ((c = (unsigned char)*(hex++))) { + if ((c >= 48)&&(c <= 57)) { // 0..9 + if ((n ^= 1)) + r.push_back((char)(b | (c - 48))); + else b = (c - 48) << 4; + } else if ((c >= 65)&&(c <= 70)) { // A..F + if ((n ^= 1)) + r.push_back((char)(b | (c - (65 - 10)))); + else b = (c - (65 - 10)) << 4; + } else if ((c >= 97)&&(c <= 102)) { // a..f + if ((n ^= 1)) + r.push_back((char)(b | (c - (97 - 10)))); + else b = (c - (97 - 10)) << 4; + } + if (hex == eof) + break; + } + + return r; +} + +unsigned int Utils::unhex(const char *hex,unsigned int maxlen,void *buf,unsigned int len) +{ + int n = 1; + unsigned char c,b = 0; + unsigned int l = 0; + const char *eof = hex + maxlen; + + if (!maxlen) + return 0; + + while ((c = (unsigned char)*(hex++))) { + if ((c >= 48)&&(c <= 57)) { // 0..9 + if ((n ^= 1)) { + if (l >= len) break; + ((unsigned char *)buf)[l++] = (b | (c - 48)); + } else b = (c - 48) << 4; + } else if ((c >= 65)&&(c <= 70)) { // A..F + if ((n ^= 1)) { + if (l >= len) break; + ((unsigned char *)buf)[l++] = (b | (c - (65 - 10))); + } else b = (c - (65 - 10)) << 4; + } else if ((c >= 97)&&(c <= 102)) { // a..f + if ((n ^= 1)) { + if (l >= len) break; + ((unsigned char *)buf)[l++] = (b | (c - (97 - 10))); + } else b = (c - (97 - 10)) << 4; + } + if (hex == eof) + break; + } + + return l; +} + +void Utils::getSecureRandom(void *buf,unsigned int bytes) +{ + static Mutex globalLock; + static Salsa20 s20; + static bool s20Initialized = false; + static uint8_t randomBuf[65536]; + static unsigned int randomPtr = sizeof(randomBuf); + + Mutex::Lock _l(globalLock); + + /* Just for posterity we Salsa20 encrypt the result of whatever system + * CSPRNG we use. There have been several bugs at the OS or OS distribution + * level in the past that resulted in systematically weak or predictable + * keys due to random seeding problems. This mitigates that by grabbing + * a bit of extra entropy and further randomizing the result, and comes + * at almost no cost and with no real downside if the random source is + * good. */ + if (!s20Initialized) { + s20Initialized = true; + uint64_t s20Key[4]; + s20Key[0] = (uint64_t)time(0); // system clock + s20Key[1] = (uint64_t)buf; // address of buf + s20Key[2] = (uint64_t)s20Key; // address of s20Key[] + s20Key[3] = (uint64_t)&s20; // address of s20 + s20.init(s20Key,s20Key); + } + +#ifdef __WINDOWS__ + + static HCRYPTPROV cryptProvider = NULL; + + for(unsigned int i=0;i= sizeof(randomBuf)) { + if (cryptProvider == NULL) { + if (!CryptAcquireContextA(&cryptProvider,NULL,NULL,PROV_RSA_FULL,CRYPT_VERIFYCONTEXT|CRYPT_SILENT)) { + fprintf(stderr,"FATAL ERROR: Utils::getSecureRandom() unable to obtain WinCrypt context!\r\n"); + exit(1); + } + } + if (!CryptGenRandom(cryptProvider,(DWORD)sizeof(randomBuf),(BYTE *)randomBuf)) { + fprintf(stderr,"FATAL ERROR: Utils::getSecureRandom() CryptGenRandom failed!\r\n"); + exit(1); + } + randomPtr = 0; + s20.crypt12(randomBuf,randomBuf,sizeof(randomBuf)); + s20.init(randomBuf,randomBuf); + } + ((uint8_t *)buf)[i] = randomBuf[randomPtr++]; + } + +#else // not __WINDOWS__ + + static int devURandomFd = -1; + + if (devURandomFd < 0) { + devURandomFd = ::open("/dev/urandom",O_RDONLY); + if (devURandomFd < 0) { + fprintf(stderr,"FATAL ERROR: Utils::getSecureRandom() unable to open /dev/urandom\n"); + exit(1); + return; + } + } + + for(unsigned int i=0;i= sizeof(randomBuf)) { + for(;;) { + if ((int)::read(devURandomFd,randomBuf,sizeof(randomBuf)) != (int)sizeof(randomBuf)) { + ::close(devURandomFd); + devURandomFd = ::open("/dev/urandom",O_RDONLY); + if (devURandomFd < 0) { + fprintf(stderr,"FATAL ERROR: Utils::getSecureRandom() unable to open /dev/urandom\n"); + exit(1); + return; + } + } else break; + } + randomPtr = 0; + s20.crypt12(randomBuf,randomBuf,sizeof(randomBuf)); + s20.init(randomBuf,randomBuf); + } + ((uint8_t *)buf)[i] = randomBuf[randomPtr++]; + } + +#endif // __WINDOWS__ or not +} + +bool Utils::scopy(char *dest,unsigned int len,const char *src) +{ + if (!len) + return false; // sanity check + if (!src) { + *dest = (char)0; + return true; + } + char *end = dest + len; + while ((*dest++ = *src++)) { + if (dest == end) { + *(--dest) = (char)0; + return false; + } + } + return true; +} + +unsigned int Utils::snprintf(char *buf,unsigned int len,const char *fmt,...) + throw(std::length_error) +{ + va_list ap; + + va_start(ap,fmt); + int n = (int)vsnprintf(buf,len,fmt,ap); + va_end(ap); + + if ((n >= (int)len)||(n < 0)) { + if (len) + buf[len - 1] = (char)0; + throw std::length_error("buf[] overflow in Utils::snprintf"); + } + + return (unsigned int)n; +} + +} // namespace ZeroTier diff --git a/node/Utils.hpp b/node/Utils.hpp new file mode 100644 index 0000000..ceb29d7 --- /dev/null +++ b/node/Utils.hpp @@ -0,0 +1,386 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_UTILS_HPP +#define ZT_UTILS_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "Constants.hpp" + +namespace ZeroTier { + +/** + * Miscellaneous utility functions and global constants + */ +class Utils +{ +public: + /** + * Perform a time-invariant binary comparison + * + * @param a First binary string + * @param b Second binary string + * @param len Length of strings + * @return True if strings are equal + */ + static inline bool secureEq(const void *a,const void *b,unsigned int len) + { + uint8_t diff = 0; + for(unsigned int i=0;i(a))[i] ^ (reinterpret_cast(b))[i] ); + return (diff == 0); + } + + /** + * Securely zero memory, avoiding compiler optimizations and such + */ + static void burn(void *ptr,unsigned int len); + + /** + * Convert binary data to hexadecimal + * + * @param data Data to convert to hex + * @param len Length of data + * @return Hexadecimal string + */ + static std::string hex(const void *data,unsigned int len); + static inline std::string hex(const std::string &data) { return hex(data.data(),(unsigned int)data.length()); } + + /** + * Convert hexadecimal to binary data + * + * This ignores all non-hex characters, just stepping over them and + * continuing. Upper and lower case are supported for letters a-f. + * + * @param hex Hexadecimal ASCII code (non-hex chars are ignored, stops at zero or maxlen) + * @param maxlen Maximum length of hex string buffer + * @return Binary data + */ + static std::string unhex(const char *hex,unsigned int maxlen); + static inline std::string unhex(const std::string &hex) { return unhex(hex.c_str(),(unsigned int)hex.length()); } + + /** + * Convert hexadecimal to binary data + * + * This ignores all non-hex characters, just stepping over them and + * continuing. Upper and lower case are supported for letters a-f. + * + * @param hex Hexadecimal ASCII + * @param maxlen Maximum length of hex string buffer + * @param buf Buffer to fill + * @param len Length of buffer + * @return Number of characters actually written + */ + static unsigned int unhex(const char *hex,unsigned int maxlen,void *buf,unsigned int len); + static inline unsigned int unhex(const std::string &hex,void *buf,unsigned int len) { return unhex(hex.c_str(),(unsigned int)hex.length(),buf,len); } + + /** + * Generate secure random bytes + * + * This will try to use whatever OS sources of entropy are available. It's + * guarded by an internal mutex so it's thread-safe. + * + * @param buf Buffer to fill + * @param bytes Number of random bytes to generate + */ + static void getSecureRandom(void *buf,unsigned int bytes); + + /** + * Tokenize a string (alias for strtok_r or strtok_s depending on platform) + * + * @param str String to split + * @param delim Delimiters + * @param saveptr Pointer to a char * for temporary reentrant storage + */ + static inline char *stok(char *str,const char *delim,char **saveptr) + throw() + { +#ifdef __WINDOWS__ + return strtok_s(str,delim,saveptr); +#else + return strtok_r(str,delim,saveptr); +#endif + } + + // String to number converters -- defined here to permit portability + // ifdefs for platforms that lack some of the strtoXX functions. + static inline unsigned int strToUInt(const char *s) + throw() + { + return (unsigned int)strtoul(s,(char **)0,10); + } + static inline int strToInt(const char *s) + throw() + { + return (int)strtol(s,(char **)0,10); + } + static inline unsigned long strToULong(const char *s) + throw() + { + return strtoul(s,(char **)0,10); + } + static inline long strToLong(const char *s) + throw() + { + return strtol(s,(char **)0,10); + } + static inline unsigned long long strToU64(const char *s) + throw() + { +#ifdef __WINDOWS__ + return (unsigned long long)_strtoui64(s,(char **)0,10); +#else + return strtoull(s,(char **)0,10); +#endif + } + static inline long long strTo64(const char *s) + throw() + { +#ifdef __WINDOWS__ + return (long long)_strtoi64(s,(char **)0,10); +#else + return strtoll(s,(char **)0,10); +#endif + } + static inline unsigned int hexStrToUInt(const char *s) + throw() + { + return (unsigned int)strtoul(s,(char **)0,16); + } + static inline int hexStrToInt(const char *s) + throw() + { + return (int)strtol(s,(char **)0,16); + } + static inline unsigned long hexStrToULong(const char *s) + throw() + { + return strtoul(s,(char **)0,16); + } + static inline long hexStrToLong(const char *s) + throw() + { + return strtol(s,(char **)0,16); + } + static inline unsigned long long hexStrToU64(const char *s) + throw() + { +#ifdef __WINDOWS__ + return (unsigned long long)_strtoui64(s,(char **)0,16); +#else + return strtoull(s,(char **)0,16); +#endif + } + static inline long long hexStrTo64(const char *s) + throw() + { +#ifdef __WINDOWS__ + return (long long)_strtoi64(s,(char **)0,16); +#else + return strtoll(s,(char **)0,16); +#endif + } + static inline double strToDouble(const char *s) + throw() + { + return strtod(s,(char **)0); + } + + /** + * Perform a safe C string copy, ALWAYS null-terminating the result + * + * This will never ever EVER result in dest[] not being null-terminated + * regardless of any input parameter (other than len==0 which is invalid). + * + * @param dest Destination buffer (must not be NULL) + * @param len Length of dest[] (if zero, false is returned and nothing happens) + * @param src Source string (if NULL, dest will receive a zero-length string and true is returned) + * @return True on success, false on overflow (buffer will still be 0-terminated) + */ + static bool scopy(char *dest,unsigned int len,const char *src); + + /** + * Variant of snprintf that is portable and throws an exception + * + * This just wraps the local implementation whatever it's called, while + * performing a few other checks and adding exceptions for overflow. + * + * @param buf Buffer to write to + * @param len Length of buffer in bytes + * @param fmt Format string + * @param ... Format arguments + * @throws std::length_error buf[] too short (buf[] will still be left null-terminated) + */ + static unsigned int snprintf(char *buf,unsigned int len,const char *fmt,...) + throw(std::length_error); + + /** + * Count the number of bits set in an integer + * + * @param v 32-bit integer + * @return Number of bits set in this integer (0-32) + */ + static inline uint32_t countBits(uint32_t v) + { + v = v - ((v >> 1) & (uint32_t)0x55555555); + v = (v & (uint32_t)0x33333333) + ((v >> 2) & (uint32_t)0x33333333); + return ((((v + (v >> 4)) & (uint32_t)0xF0F0F0F) * (uint32_t)0x1010101) >> 24); + } + + /** + * Count the number of bits set in an integer + * + * @param v 64-bit integer + * @return Number of bits set in this integer (0-64) + */ + static inline uint64_t countBits(uint64_t v) + { + v = v - ((v >> 1) & (uint64_t)~(uint64_t)0/3); + v = (v & (uint64_t)~(uint64_t)0/15*3) + ((v >> 2) & (uint64_t)~(uint64_t)0/15*3); + v = (v + (v >> 4)) & (uint64_t)~(uint64_t)0/255*15; + return (uint64_t)(v * ((uint64_t)~(uint64_t)0/255)) >> 56; + } + + /** + * Check if a memory buffer is all-zero + * + * @param p Memory to scan + * @param len Length of memory + * @return True if memory is all zero + */ + static inline bool isZero(const void *p,unsigned int len) + { + for(unsigned int i=0;i> 8) | + ((n & 0x0000FF0000000000ULL) >> 24) | + ((n & 0x00FF000000000000ULL) >> 40) | + ((n & 0xFF00000000000000ULL) >> 56) + ); +#endif +#else + return n; +#endif + } + static inline int64_t hton(int64_t n) throw() { return (int64_t)hton((uint64_t)n); } + + static inline uint8_t ntoh(uint8_t n) throw() { return n; } + static inline int8_t ntoh(int8_t n) throw() { return n; } + static inline uint16_t ntoh(uint16_t n) throw() { return ntohs(n); } + static inline int16_t ntoh(int16_t n) throw() { return (int16_t)ntohs((uint16_t)n); } + static inline uint32_t ntoh(uint32_t n) throw() { return ntohl(n); } + static inline int32_t ntoh(int32_t n) throw() { return (int32_t)ntohl((uint32_t)n); } + static inline uint64_t ntoh(uint64_t n) + throw() + { +#if __BYTE_ORDER == __LITTLE_ENDIAN +#if defined(__GNUC__) && !defined(__OpenBSD__) + return __builtin_bswap64(n); +#else + return ( + ((n & 0x00000000000000FFULL) << 56) | + ((n & 0x000000000000FF00ULL) << 40) | + ((n & 0x0000000000FF0000ULL) << 24) | + ((n & 0x00000000FF000000ULL) << 8) | + ((n & 0x000000FF00000000ULL) >> 8) | + ((n & 0x0000FF0000000000ULL) >> 24) | + ((n & 0x00FF000000000000ULL) >> 40) | + ((n & 0xFF00000000000000ULL) >> 56) + ); +#endif +#else + return n; +#endif + } + static inline int64_t ntoh(int64_t n) throw() { return (int64_t)ntoh((uint64_t)n); } + + /** + * Compare Peer version tuples + * + * @return -1, 0, or 1 based on whether first tuple is less than, equal to, or greater than second + */ + static inline int compareVersion(unsigned int maj1,unsigned int min1,unsigned int rev1,unsigned int b1,unsigned int maj2,unsigned int min2,unsigned int rev2,unsigned int b2) + { + if (maj1 > maj2) + return 1; + else if (maj1 < maj2) + return -1; + else { + if (min1 > min2) + return 1; + else if (min1 < min2) + return -1; + else { + if (rev1 > rev2) + return 1; + else if (rev1 < rev2) + return -1; + else { + if (b1 > b2) + return 1; + else if (b1 < b2) + return -1; + else return 0; + } + } + } + } + + /** + * Hexadecimal characters 0-f + */ + static const char HEXCHARS[16]; +}; + +} // namespace ZeroTier + +#endif diff --git a/node/World.hpp b/node/World.hpp new file mode 100644 index 0000000..6e835be --- /dev/null +++ b/node/World.hpp @@ -0,0 +1,278 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_WORLD_HPP +#define ZT_WORLD_HPP + +#include +#include + +#include "Constants.hpp" +#include "InetAddress.hpp" +#include "Identity.hpp" +#include "Buffer.hpp" +#include "C25519.hpp" + +/** + * Maximum number of roots (sanity limit, okay to increase) + * + * A given root can (through multi-homing) be distributed across any number of + * physical endpoints, but having more than one is good to permit total failure + * of one root or its withdrawal due to compromise without taking the whole net + * down. + */ +#define ZT_WORLD_MAX_ROOTS 4 + +/** + * Maximum number of stable endpoints per root (sanity limit, okay to increase) + */ +#define ZT_WORLD_MAX_STABLE_ENDPOINTS_PER_ROOT 32 + +/** + * The (more than) maximum length of a serialized World + */ +#define ZT_WORLD_MAX_SERIALIZED_LENGTH (((1024 + (32 * ZT_WORLD_MAX_STABLE_ENDPOINTS_PER_ROOT)) * ZT_WORLD_MAX_ROOTS) + ZT_C25519_PUBLIC_KEY_LEN + ZT_C25519_SIGNATURE_LEN + 128) + +/** + * World ID for Earth + * + * This is the ID for the ZeroTier World used on planet Earth. It is unrelated + * to the public network 8056c2e21c000001 of the same name. It was chosen + * from Earth's approximate distance from the sun in kilometers. + */ +#define ZT_WORLD_ID_EARTH 149604618 + +/** + * World ID for Mars -- for future use by SpaceX or others + */ +#define ZT_WORLD_ID_MARS 227883110 + +namespace ZeroTier { + +/** + * A world definition (formerly known as a root topology) + * + * Think of a World as a single data center. Within this data center a set + * of distributed fault tolerant root servers provide stable anchor points + * for a peer to peer network that provides VLAN service. Updates to a world + * definition can be published by signing them with the previous revision's + * signing key, and should be very infrequent. + * + * The maximum data center size is approximately 2.5 cubic light seconds, + * since many protocols have issues with >5s RTT latencies. + * + * ZeroTier operates a World for Earth capable of encompassing the planet, its + * orbits, the Moon (about 1.3 light seconds), and nearby Lagrange points. A + * world ID for Mars and nearby space is defined but not yet used, and a test + * world ID is provided for testing purposes. + */ +class World +{ +public: + /** + * World type -- do not change IDs + */ + enum Type + { + TYPE_NULL = 0, + TYPE_PLANET = 1, // Planets, of which there is currently one (Earth) + TYPE_MOON = 127 // Moons, which are user-created and many + }; + + /** + * Upstream server definition in world/moon + */ + struct Root + { + Identity identity; + std::vector stableEndpoints; + + inline bool operator==(const Root &r) const throw() { return ((identity == r.identity)&&(stableEndpoints == r.stableEndpoints)); } + inline bool operator!=(const Root &r) const throw() { return (!(*this == r)); } + inline bool operator<(const Root &r) const throw() { return (identity < r.identity); } // for sorting + }; + + /** + * Construct an empty / null World + */ + World() : + _id(0), + _ts(0), + _type(TYPE_NULL) {} + + /** + * @return Root servers for this world and their stable endpoints + */ + inline const std::vector &roots() const { return _roots; } + + /** + * @return World type: planet or moon + */ + inline Type type() const { return _type; } + + /** + * @return World unique identifier + */ + inline uint64_t id() const { return _id; } + + /** + * @return World definition timestamp + */ + inline uint64_t timestamp() const { return _ts; } + + /** + * @return C25519 signature + */ + inline const C25519::Signature &signature() const { return _signature; } + + /** + * @return Public key that must sign next update + */ + inline const C25519::Public &updatesMustBeSignedBy() const { return _updatesMustBeSignedBy; } + + /** + * Check whether a world update should replace this one + * + * @param update Candidate update + * @return True if update is newer than current, matches its ID and type, and is properly signed (or if current is NULL) + */ + inline bool shouldBeReplacedBy(const World &update) + { + if ((_id == 0)||(_type == TYPE_NULL)) + return true; + if ((_id == update._id)&&(_ts < update._ts)&&(_type == update._type)) { + Buffer tmp; + update.serialize(tmp,true); + return C25519::verify(_updatesMustBeSignedBy,tmp.data(),tmp.size(),update._signature); + } + return false; + } + + /** + * @return True if this World is non-empty + */ + inline operator bool() const { return (_type != TYPE_NULL); } + + template + inline void serialize(Buffer &b,bool forSign = false) const + { + if (forSign) b.append((uint64_t)0x7f7f7f7f7f7f7f7fULL); + + b.append((uint8_t)_type); + b.append((uint64_t)_id); + b.append((uint64_t)_ts); + b.append(_updatesMustBeSignedBy.data,ZT_C25519_PUBLIC_KEY_LEN); + if (!forSign) + b.append(_signature.data,ZT_C25519_SIGNATURE_LEN); + b.append((uint8_t)_roots.size()); + for(std::vector::const_iterator r(_roots.begin());r!=_roots.end();++r) { + r->identity.serialize(b); + b.append((uint8_t)r->stableEndpoints.size()); + for(std::vector::const_iterator ep(r->stableEndpoints.begin());ep!=r->stableEndpoints.end();++ep) + ep->serialize(b); + } + if (_type == TYPE_MOON) + b.append((uint16_t)0); // no attached dictionary (for future use) + + if (forSign) b.append((uint64_t)0xf7f7f7f7f7f7f7f7ULL); + } + + template + inline unsigned int deserialize(const Buffer &b,unsigned int startAt = 0) + { + unsigned int p = startAt; + + _roots.clear(); + + switch((Type)b[p++]) { + case TYPE_NULL: _type = TYPE_NULL; break; // shouldn't ever really happen in serialized data but it's not invalid + case TYPE_PLANET: _type = TYPE_PLANET; break; + case TYPE_MOON: _type = TYPE_MOON; break; + default: + throw std::invalid_argument("invalid world type"); + } + + _id = b.template at(p); p += 8; + _ts = b.template at(p); p += 8; + memcpy(_updatesMustBeSignedBy.data,b.field(p,ZT_C25519_PUBLIC_KEY_LEN),ZT_C25519_PUBLIC_KEY_LEN); p += ZT_C25519_PUBLIC_KEY_LEN; + memcpy(_signature.data,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN); p += ZT_C25519_SIGNATURE_LEN; + const unsigned int numRoots = (unsigned int)b[p++]; + if (numRoots > ZT_WORLD_MAX_ROOTS) + throw std::invalid_argument("too many roots in World"); + for(unsigned int k=0;k ZT_WORLD_MAX_STABLE_ENDPOINTS_PER_ROOT) + throw std::invalid_argument("too many stable endpoints in World/Root"); + for(unsigned int kk=0;kk(p) + 2; + + return (p - startAt); + } + + inline bool operator==(const World &w) const { return ((_id == w._id)&&(_ts == w._ts)&&(_updatesMustBeSignedBy == w._updatesMustBeSignedBy)&&(_signature == w._signature)&&(_roots == w._roots)&&(_type == w._type)); } + inline bool operator!=(const World &w) const { return (!(*this == w)); } + + inline bool operator<(const World &w) const { return (((int)_type < (int)w._type) ? true : ((_type == w._type) ? (_id < w._id) : false)); } + + /** + * Create a World object signed with a key pair + * + * @param t World type + * @param id World ID + * @param ts World timestamp / revision + * @param sk Key that must be used to sign the next future update to this world + * @param roots Roots and their stable endpoints + * @param signWith Key to sign this World with (can have the same public as the next-update signing key, but doesn't have to) + * @return Signed World object + */ + static inline World make(World::Type t,uint64_t id,uint64_t ts,const C25519::Public &sk,const std::vector &roots,const C25519::Pair &signWith) + { + World w; + w._id = id; + w._ts = ts; + w._type = t; + w._updatesMustBeSignedBy = sk; + w._roots = roots; + + Buffer tmp; + w.serialize(tmp,true); + w._signature = C25519::sign(signWith,tmp.data(),tmp.size()); + + return w; + } + +protected: + uint64_t _id; + uint64_t _ts; + Type _type; + C25519::Public _updatesMustBeSignedBy; + C25519::Signature _signature; + std::vector _roots; +}; + +} // namespace ZeroTier + +#endif diff --git a/objects.mk b/objects.mk new file mode 100644 index 0000000..74efc33 --- /dev/null +++ b/objects.mk @@ -0,0 +1,34 @@ +OBJS=\ + controller/EmbeddedNetworkController.o \ + controller/JSONDB.o \ + node/C25519.o \ + node/Capability.o \ + node/CertificateOfMembership.o \ + node/CertificateOfOwnership.o \ + node/Cluster.o \ + node/Identity.o \ + node/IncomingPacket.o \ + node/InetAddress.o \ + node/Membership.o \ + node/Multicaster.o \ + node/Network.o \ + node/NetworkConfig.o \ + node/Node.o \ + node/OutboundMulticast.o \ + node/Packet.o \ + node/Path.o \ + node/Peer.o \ + node/Poly1305.o \ + node/Revocation.o \ + node/Salsa20.o \ + node/SelfAwareness.o \ + node/SHA512.o \ + node/Switch.o \ + node/Tag.o \ + node/Topology.o \ + node/Utils.o \ + osdep/ManagedRoute.o \ + osdep/Http.o \ + osdep/OSUtils.o \ + service/ClusterGeoIpService.o \ + service/SoftwareUpdater.o diff --git a/one.cpp b/one.cpp new file mode 100644 index 0000000..b40e28f --- /dev/null +++ b/one.cpp @@ -0,0 +1,1506 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include "node/Constants.hpp" + +#ifdef __WINDOWS__ +#include +#include +#include +#include +#include +#include +#include +#include "osdep/WindowsEthernetTap.hpp" +#include "windows/ZeroTierOne/ServiceInstaller.h" +#include "windows/ZeroTierOne/ServiceBase.h" +#include "windows/ZeroTierOne/ZeroTierOneService.h" +#else +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __LINUX__ +#include +#include +#include +#include +#include +#endif +#endif + +#include +#include +#include +#include + +#include "version.h" +#include "include/ZeroTierOne.h" + +#include "node/Identity.hpp" +#include "node/CertificateOfMembership.hpp" +#include "node/Utils.hpp" +#include "node/NetworkController.hpp" +#include "node/Buffer.hpp" +#include "node/World.hpp" + +#include "osdep/OSUtils.hpp" +#include "osdep/Http.hpp" +#include "osdep/Thread.hpp" + +#include "service/OneService.hpp" + +#include "ext/json/json.hpp" + +#define ZT_PID_PATH "zerotier-one.pid" + +using namespace ZeroTier; + +static OneService *volatile zt1Service = (OneService *)0; + +#define PROGRAM_NAME "ZeroTier One" +#define COPYRIGHT_NOTICE "Copyright (c) 2011-2017 ZeroTier, Inc." +#define LICENSE_GRANT \ + "This is free software: you may copy, modify, and/or distribute this" ZT_EOL_S \ + "work under the terms of the GNU General Public License, version 3 or" ZT_EOL_S \ + "later as published by the Free Software Foundation." ZT_EOL_S \ + "No warranty expressed or implied." ZT_EOL_S + +/****************************************************************************/ +/* zerotier-cli personality */ +/****************************************************************************/ + +// This is getting deprecated soon in favor of the stuff in cli/ + +static void cliPrintHelp(const char *pn,FILE *out) +{ + fprintf(out, + "%s version %d.%d.%d build %d (platform %d arch %d)" ZT_EOL_S, + PROGRAM_NAME, + ZEROTIER_ONE_VERSION_MAJOR, ZEROTIER_ONE_VERSION_MINOR, ZEROTIER_ONE_VERSION_REVISION, ZEROTIER_ONE_VERSION_BUILD, + ZT_BUILD_PLATFORM, ZT_BUILD_ARCHITECTURE); + fprintf(out, + COPYRIGHT_NOTICE ZT_EOL_S + LICENSE_GRANT ZT_EOL_S); + fprintf(out,"Usage: %s [-switches] []" ZT_EOL_S"" ZT_EOL_S,pn); + fprintf(out,"Available switches:" ZT_EOL_S); + fprintf(out," -h - Display this help" ZT_EOL_S); + fprintf(out," -v - Show version" ZT_EOL_S); + fprintf(out," -j - Display full raw JSON output" ZT_EOL_S); + fprintf(out," -D - ZeroTier home path for parameter auto-detect" ZT_EOL_S); + fprintf(out," -p - HTTP port (default: auto)" ZT_EOL_S); + fprintf(out," -T - Authentication token (default: auto)" ZT_EOL_S); + fprintf(out,ZT_EOL_S"Available commands:" ZT_EOL_S); + fprintf(out," info - Display status info" ZT_EOL_S); + fprintf(out," listpeers - List all peers" ZT_EOL_S); + fprintf(out," listnetworks - List all networks" ZT_EOL_S); + fprintf(out," join - Join a network" ZT_EOL_S); + fprintf(out," leave - Leave a network" ZT_EOL_S); + fprintf(out," set - Set a network setting" ZT_EOL_S); + fprintf(out," listmoons - List moons (federated root sets)" ZT_EOL_S); + fprintf(out," orbit - Join a moon via any member root" ZT_EOL_S); + fprintf(out," deorbit - Leave a moon" ZT_EOL_S); +} + +static std::string cliFixJsonCRs(const std::string &s) +{ + std::string r; + for(std::string::const_iterator c(s.begin());c!=s.end();++c) { + if (*c == '\n') + r.append(ZT_EOL_S); + else r.push_back(*c); + } + return r; +} + +#ifdef __WINDOWS__ +static int cli(int argc, _TCHAR* argv[]) +#else +static int cli(int argc,char **argv) +#endif +{ + unsigned int port = 0; + std::string homeDir,command,arg1,arg2,authToken; + std::string ip("127.0.0.1"); + bool json = false; + for(int i=1;i 0xffff)||(port == 0)) { + cliPrintHelp(argv[0],stdout); + return 1; + } + break; + + case 'D': + if (argv[i][2]) { + homeDir = argv[i] + 2; + } else { + cliPrintHelp(argv[0],stdout); + return 1; + } + break; + + case 'H': + if (argv[i][2]) { + ip = argv[i] + 2; + } else { + cliPrintHelp(argv[0],stdout); + return 1; + } + break; + + case 'T': + if (argv[i][2]) { + authToken = argv[i] + 2; + } else { + cliPrintHelp(argv[0],stdout); + return 1; + } + break; + + case 'v': + if (argv[i][2]) { + cliPrintHelp(argv[0],stdout); + return 1; + } + printf("%d.%d.%d" ZT_EOL_S,ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION); + return 0; + + case 'h': + case '?': + default: + cliPrintHelp(argv[0],stdout); + return 0; + } + } else { + if (arg1.length()) + arg2 = argv[i]; + else if (command.length()) + arg1 = argv[i]; + else command = argv[i]; + } + } + if (!homeDir.length()) + homeDir = OneService::platformDefaultHomePath(); + + if ((!port)||(!authToken.length())) { + if (!homeDir.length()) { + fprintf(stderr,"%s: missing port or authentication token and no home directory specified to auto-detect" ZT_EOL_S,argv[0]); + return 2; + } + + if (!port) { + std::string portStr; + OSUtils::readFile((homeDir + ZT_PATH_SEPARATOR_S + "zerotier-one.port").c_str(),portStr); + port = Utils::strToUInt(portStr.c_str()); + if ((port == 0)||(port > 0xffff)) { + fprintf(stderr,"%s: missing port and zerotier-one.port not found in %s" ZT_EOL_S,argv[0],homeDir.c_str()); + return 2; + } + } + + if (!authToken.length()) { + OSUtils::readFile((homeDir + ZT_PATH_SEPARATOR_S + "authtoken.secret").c_str(),authToken); +#ifdef __UNIX_LIKE__ + if (!authToken.length()) { + const char *hd = getenv("HOME"); + if (hd) { + char p[4096]; +#ifdef __APPLE__ + Utils::snprintf(p,sizeof(p),"%s/Library/Application Support/ZeroTier/One/authtoken.secret",hd); +#else + Utils::snprintf(p,sizeof(p),"%s/.zeroTierOneAuthToken",hd); +#endif + OSUtils::readFile(p,authToken); + } + } +#endif + if (!authToken.length()) { + fprintf(stderr,"%s: missing authentication token and authtoken.secret not found (or readable) in %s" ZT_EOL_S,argv[0],homeDir.c_str()); + return 2; + } + } + } + + InetAddress addr; + { + char addrtmp[256]; + Utils::snprintf(addrtmp,sizeof(addrtmp),"%s/%u",ip.c_str(),port); + addr = InetAddress(addrtmp); + } + + std::map requestHeaders; + std::map responseHeaders; + std::string responseBody; + + requestHeaders["X-ZT1-Auth"] = authToken; + + if ((command.length() > 0)&&(command[0] == '/')) { + unsigned int scode = Http::GET( + 1024 * 1024 * 16, + 60000, + (const struct sockaddr *)&addr, + command.c_str(), + requestHeaders, + responseHeaders, + responseBody); + if (scode == 200) { + printf("%s",cliFixJsonCRs(responseBody).c_str()); + return 0; + } else { + printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); + return 1; + } + } else if ((command == "info")||(command == "status")) { + const unsigned int scode = Http::GET(1024 * 1024 * 16,60000,(const struct sockaddr *)&addr,"/status",requestHeaders,responseHeaders,responseBody); + + nlohmann::json j; + try { + j = OSUtils::jsonParse(responseBody); + } catch (std::exception &exc) { + printf("%u %s invalid JSON response (%s)" ZT_EOL_S,scode,command.c_str(),exc.what()); + return 1; + } catch ( ... ) { + printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S,scode,command.c_str()); + return 1; + } + + if (scode == 200) { + if (json) { + printf("%s" ZT_EOL_S,OSUtils::jsonDump(j).c_str()); + } else { + if (j.is_object()) { + printf("200 info %s %s %s" ZT_EOL_S, + OSUtils::jsonString(j["address"],"-").c_str(), + OSUtils::jsonString(j["version"],"-").c_str(), + ((j["tcpFallbackActive"]) ? "TUNNELED" : ((j["online"]) ? "ONLINE" : "OFFLINE"))); + } + } + return 0; + } else { + printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); + return 1; + } + } else if (command == "listpeers") { + const unsigned int scode = Http::GET(1024 * 1024 * 16,60000,(const struct sockaddr *)&addr,"/peer",requestHeaders,responseHeaders,responseBody); + + nlohmann::json j; + try { + j = OSUtils::jsonParse(responseBody); + } catch (std::exception &exc) { + printf("%u %s invalid JSON response (%s)" ZT_EOL_S,scode,command.c_str(),exc.what()); + return 1; + } catch ( ... ) { + printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S,scode,command.c_str()); + return 1; + } + + if (scode == 200) { + if (json) { + printf("%s" ZT_EOL_S,OSUtils::jsonDump(j).c_str()); + } else { + printf("200 listpeers " ZT_EOL_S); + if (j.is_array()) { + for(unsigned long k=0;k= 0) { + Utils::snprintf(ver,sizeof(ver),"%lld.%lld.%lld",vmaj,vmin,vrev); + } else { + ver[0] = '-'; + ver[1] = (char)0; + } + printf("200 listpeers %s %s %d %s %s" ZT_EOL_S, + OSUtils::jsonString(p["address"],"-").c_str(), + bestPath.c_str(), + (int)OSUtils::jsonInt(p["latency"],0), + ver, + OSUtils::jsonString(p["role"],"-").c_str()); + } + } + } + return 0; + } else { + printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); + return 1; + } + } else if (command == "listnetworks") { + const unsigned int scode = Http::GET(1024 * 1024 * 16,60000,(const struct sockaddr *)&addr,"/network",requestHeaders,responseHeaders,responseBody); + + nlohmann::json j; + try { + j = OSUtils::jsonParse(responseBody); + } catch (std::exception &exc) { + printf("%u %s invalid JSON response (%s)" ZT_EOL_S,scode,command.c_str(),exc.what()); + return 1; + } catch ( ... ) { + printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S,scode,command.c_str()); + return 1; + } + + if (scode == 200) { + if (json) { + printf("%s" ZT_EOL_S,OSUtils::jsonDump(j).c_str()); + } else { + printf("200 listnetworks " ZT_EOL_S); + if (j.is_array()) { + for(unsigned long i=0;i 0) aa.push_back(','); + aa.append(addr.get()); + } + } + } + if (aa.length() == 0) aa = "-"; + printf("200 listnetworks %s %s %s %s %s %s %s" ZT_EOL_S, + OSUtils::jsonString(n["nwid"],"-").c_str(), + OSUtils::jsonString(n["name"],"-").c_str(), + OSUtils::jsonString(n["mac"],"-").c_str(), + OSUtils::jsonString(n["status"],"-").c_str(), + OSUtils::jsonString(n["type"],"-").c_str(), + OSUtils::jsonString(n["portDeviceName"],"-").c_str(), + aa.c_str()); + } + } + } + } + return 0; + } else { + printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); + return 1; + } + } else if (command == "join") { + if (arg1.length() != 16) { + cliPrintHelp(argv[0],stderr); + return 2; + } + requestHeaders["Content-Type"] = "application/json"; + requestHeaders["Content-Length"] = "2"; + unsigned int scode = Http::POST( + 1024 * 1024 * 16, + 60000, + (const struct sockaddr *)&addr, + (std::string("/network/") + arg1).c_str(), + requestHeaders, + "{}", + 2, + responseHeaders, + responseBody); + if (scode == 200) { + if (json) { + printf("%s",cliFixJsonCRs(responseBody).c_str()); + } else { + printf("200 join OK" ZT_EOL_S); + } + return 0; + } else { + printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); + return 1; + } + } else if (command == "leave") { + if (arg1.length() != 16) { + cliPrintHelp(argv[0],stderr); + return 2; + } + unsigned int scode = Http::DEL( + 1024 * 1024 * 16, + 60000, + (const struct sockaddr *)&addr, + (std::string("/network/") + arg1).c_str(), + requestHeaders, + responseHeaders, + responseBody); + if (scode == 200) { + if (json) { + printf("%s",cliFixJsonCRs(responseBody).c_str()); + } else { + printf("200 leave OK" ZT_EOL_S); + } + return 0; + } else { + printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); + return 1; + } + } else if (command == "listmoons") { + const unsigned int scode = Http::GET(1024 * 1024 * 16,60000,(const struct sockaddr *)&addr,"/moon",requestHeaders,responseHeaders,responseBody); + + nlohmann::json j; + try { + j = OSUtils::jsonParse(responseBody); + } catch (std::exception &exc) { + printf("%u %s invalid JSON response (%s)" ZT_EOL_S,scode,command.c_str(),exc.what()); + return 1; + } catch ( ... ) { + printf("%u %s invalid JSON response (unknown exception)" ZT_EOL_S,scode,command.c_str()); + return 1; + } + + if (scode == 200) { + printf("%s" ZT_EOL_S,OSUtils::jsonDump(j).c_str()); + return 0; + } else { + printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); + return 1; + } + } else if (command == "orbit") { + const uint64_t worldId = Utils::hexStrToU64(arg1.c_str()); + const uint64_t seed = Utils::hexStrToU64(arg2.c_str()); + if ((worldId)&&(seed)) { + char jsons[1024]; + Utils::snprintf(jsons,sizeof(jsons),"{\"seed\":\"%s\"}",arg2.c_str()); + char cl[128]; + Utils::snprintf(cl,sizeof(cl),"%u",(unsigned int)strlen(jsons)); + requestHeaders["Content-Type"] = "application/json"; + requestHeaders["Content-Length"] = cl; + unsigned int scode = Http::POST( + 1024 * 1024 * 16, + 60000, + (const struct sockaddr *)&addr, + (std::string("/moon/") + arg1).c_str(), + requestHeaders, + jsons, + (unsigned long)strlen(jsons), + responseHeaders, + responseBody); + if (scode == 200) { + printf("200 orbit OK" ZT_EOL_S); + return 0; + } else { + printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); + return 1; + } + } + } else if (command == "deorbit") { + unsigned int scode = Http::DEL( + 1024 * 1024 * 16, + 60000, + (const struct sockaddr *)&addr, + (std::string("/moon/") + arg1).c_str(), + requestHeaders, + responseHeaders, + responseBody); + if (scode == 200) { + if (json) { + printf("%s",cliFixJsonCRs(responseBody).c_str()); + } else { + printf("200 deorbit OK" ZT_EOL_S); + } + return 0; + } else { + printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); + return 1; + } + } else if (command == "set") { + if (arg1.length() != 16) { + cliPrintHelp(argv[0],stderr); + return 2; + } + std::size_t eqidx = arg2.find('='); + if (eqidx != std::string::npos) { + if ((arg2.substr(0,eqidx) == "allowManaged")||(arg2.substr(0,eqidx) == "allowGlobal")||(arg2.substr(0,eqidx) == "allowDefault")) { + char jsons[1024]; + Utils::snprintf(jsons,sizeof(jsons),"{\"%s\":%s}", + arg2.substr(0,eqidx).c_str(), + (((arg2.substr(eqidx,2) == "=t")||(arg2.substr(eqidx,2) == "=1")) ? "true" : "false")); + char cl[128]; + Utils::snprintf(cl,sizeof(cl),"%u",(unsigned int)strlen(jsons)); + requestHeaders["Content-Type"] = "application/json"; + requestHeaders["Content-Length"] = cl; + unsigned int scode = Http::POST( + 1024 * 1024 * 16, + 60000, + (const struct sockaddr *)&addr, + (std::string("/network/") + arg1).c_str(), + requestHeaders, + jsons, + (unsigned long)strlen(jsons), + responseHeaders, + responseBody); + if (scode == 200) { + printf("%s",cliFixJsonCRs(responseBody).c_str()); + return 0; + } else { + printf("%u %s %s" ZT_EOL_S,scode,command.c_str(),responseBody.c_str()); + return 1; + } + } + } else { + cliPrintHelp(argv[0],stderr); + return 2; + } + } else { + cliPrintHelp(argv[0],stderr); + return 0; + } + + return 0; +} + +/****************************************************************************/ +/* zerotier-idtool personality */ +/****************************************************************************/ + +static void idtoolPrintHelp(FILE *out,const char *pn) +{ + fprintf(out, + "%s version %d.%d.%d" ZT_EOL_S, + PROGRAM_NAME, + ZEROTIER_ONE_VERSION_MAJOR, ZEROTIER_ONE_VERSION_MINOR, ZEROTIER_ONE_VERSION_REVISION); + fprintf(out, + COPYRIGHT_NOTICE ZT_EOL_S + LICENSE_GRANT ZT_EOL_S); + fprintf(out,"Usage: %s []" ZT_EOL_S"" ZT_EOL_S"Commands:" ZT_EOL_S,pn); + fprintf(out," generate [] [] []" ZT_EOL_S); + fprintf(out," validate " ZT_EOL_S); + fprintf(out," getpublic " ZT_EOL_S); + fprintf(out," sign " ZT_EOL_S); + fprintf(out," verify " ZT_EOL_S); + fprintf(out," initmoon " ZT_EOL_S); + fprintf(out," genmoon " ZT_EOL_S); +} + +static Identity getIdFromArg(char *arg) +{ + Identity id; + if ((strlen(arg) > 32)&&(arg[10] == ':')) { // identity is a literal on the command line + if (id.fromString(arg)) + return id; + } else { // identity is to be read from a file + std::string idser; + if (OSUtils::readFile(arg,idser)) { + if (id.fromString(idser)) + return id; + } + } + return Identity(); +} + +#ifdef __WINDOWS__ +static int idtool(int argc, _TCHAR* argv[]) +#else +static int idtool(int argc,char **argv) +#endif +{ + if (argc < 2) { + idtoolPrintHelp(stdout,argv[0]); + return 1; + } + + if (!strcmp(argv[1],"generate")) { + uint64_t vanity = 0; + int vanityBits = 0; + if (argc >= 5) { + vanity = Utils::hexStrToU64(argv[4]) & 0xffffffffffULL; + vanityBits = 4 * (int)strlen(argv[4]); + if (vanityBits > 40) + vanityBits = 40; + } + + Identity id; + for(;;) { + id.generate(); + if ((id.address().toInt() >> (40 - vanityBits)) == vanity) { + if (vanityBits > 0) { + fprintf(stderr,"vanity address: found %.10llx !\n",(unsigned long long)id.address().toInt()); + } + break; + } else { + fprintf(stderr,"vanity address: tried %.10llx looking for first %d bits of %.10llx\n",(unsigned long long)id.address().toInt(),vanityBits,(unsigned long long)(vanity << (40 - vanityBits))); + } + } + + std::string idser = id.toString(true); + if (argc >= 3) { + if (!OSUtils::writeFile(argv[2],idser)) { + fprintf(stderr,"Error writing to %s" ZT_EOL_S,argv[2]); + return 1; + } else printf("%s written" ZT_EOL_S,argv[2]); + if (argc >= 4) { + idser = id.toString(false); + if (!OSUtils::writeFile(argv[3],idser)) { + fprintf(stderr,"Error writing to %s" ZT_EOL_S,argv[3]); + return 1; + } else printf("%s written" ZT_EOL_S,argv[3]); + } + } else printf("%s",idser.c_str()); + } else if (!strcmp(argv[1],"validate")) { + if (argc < 3) { + idtoolPrintHelp(stdout,argv[0]); + return 1; + } + + Identity id = getIdFromArg(argv[2]); + if (!id) { + fprintf(stderr,"Identity argument invalid or file unreadable: %s" ZT_EOL_S,argv[2]); + return 1; + } + + if (!id.locallyValidate()) { + fprintf(stderr,"%s FAILED validation." ZT_EOL_S,argv[2]); + return 1; + } else printf("%s is a valid identity" ZT_EOL_S,argv[2]); + } else if (!strcmp(argv[1],"getpublic")) { + if (argc < 3) { + idtoolPrintHelp(stdout,argv[0]); + return 1; + } + + Identity id = getIdFromArg(argv[2]); + if (!id) { + fprintf(stderr,"Identity argument invalid or file unreadable: %s" ZT_EOL_S,argv[2]); + return 1; + } + + printf("%s",id.toString(false).c_str()); + } else if (!strcmp(argv[1],"sign")) { + if (argc < 4) { + idtoolPrintHelp(stdout,argv[0]); + return 1; + } + + Identity id = getIdFromArg(argv[2]); + if (!id) { + fprintf(stderr,"Identity argument invalid or file unreadable: %s" ZT_EOL_S,argv[2]); + return 1; + } + + if (!id.hasPrivate()) { + fprintf(stderr,"%s does not contain a private key (must use private to sign)" ZT_EOL_S,argv[2]); + return 1; + } + + std::string inf; + if (!OSUtils::readFile(argv[3],inf)) { + fprintf(stderr,"%s is not readable" ZT_EOL_S,argv[3]); + return 1; + } + C25519::Signature signature = id.sign(inf.data(),(unsigned int)inf.length()); + printf("%s",Utils::hex(signature.data,(unsigned int)signature.size()).c_str()); + } else if (!strcmp(argv[1],"verify")) { + if (argc < 4) { + idtoolPrintHelp(stdout,argv[0]); + return 1; + } + + Identity id = getIdFromArg(argv[2]); + if (!id) { + fprintf(stderr,"Identity argument invalid or file unreadable: %s" ZT_EOL_S,argv[2]); + return 1; + } + + std::string inf; + if (!OSUtils::readFile(argv[3],inf)) { + fprintf(stderr,"%s is not readable" ZT_EOL_S,argv[3]); + return 1; + } + + std::string signature(Utils::unhex(argv[4])); + if ((signature.length() > ZT_ADDRESS_LENGTH)&&(id.verify(inf.data(),(unsigned int)inf.length(),signature.data(),(unsigned int)signature.length()))) { + printf("%s signature valid" ZT_EOL_S,argv[3]); + } else { + fprintf(stderr,"%s signature check FAILED" ZT_EOL_S,argv[3]); + return 1; + } + } else if (!strcmp(argv[1],"initmoon")) { + if (argc < 3) { + idtoolPrintHelp(stdout,argv[0]); + } else { + const Identity id = getIdFromArg(argv[2]); + if (!id) { + fprintf(stderr,"%s is not a valid identity" ZT_EOL_S,argv[2]); + return 1; + } + + C25519::Pair kp(C25519::generate()); + + nlohmann::json mj; + mj["objtype"] = "world"; + mj["worldType"] = "moon"; + mj["updatesMustBeSignedBy"] = mj["signingKey"] = Utils::hex(kp.pub.data,(unsigned int)kp.pub.size()); + mj["signingKey_SECRET"] = Utils::hex(kp.priv.data,(unsigned int)kp.priv.size()); + mj["id"] = id.address().toString(); + nlohmann::json seedj; + seedj["identity"] = id.toString(false); + seedj["stableEndpoints"] = nlohmann::json::array(); + (mj["roots"] = nlohmann::json::array()).push_back(seedj); + std::string mjd(OSUtils::jsonDump(mj)); + + printf("%s" ZT_EOL_S,mjd.c_str()); + } + } else if (!strcmp(argv[1],"genmoon")) { + if (argc < 3) { + idtoolPrintHelp(stdout,argv[0]); + } else { + std::string buf; + if (!OSUtils::readFile(argv[2],buf)) { + fprintf(stderr,"cannot read %s" ZT_EOL_S,argv[2]); + return 1; + } + nlohmann::json mj(OSUtils::jsonParse(buf)); + + const uint64_t id = Utils::hexStrToU64(OSUtils::jsonString(mj["id"],"0").c_str()); + if (!id) { + fprintf(stderr,"ID in %s is invalid" ZT_EOL_S,argv[2]); + return 1; + } + + World::Type t; + if (mj["worldType"] == "moon") { + t = World::TYPE_MOON; + } else if (mj["worldType"] == "planet") { + t = World::TYPE_PLANET; + } else { + fprintf(stderr,"invalid worldType" ZT_EOL_S); + return 1; + } + + C25519::Pair signingKey; + C25519::Public updatesMustBeSignedBy; + Utils::unhex(OSUtils::jsonString(mj["signingKey"],""),signingKey.pub.data,(unsigned int)signingKey.pub.size()); + Utils::unhex(OSUtils::jsonString(mj["signingKey_SECRET"],""),signingKey.priv.data,(unsigned int)signingKey.priv.size()); + Utils::unhex(OSUtils::jsonString(mj["updatesMustBeSignedBy"],""),updatesMustBeSignedBy.data,(unsigned int)updatesMustBeSignedBy.size()); + + std::vector roots; + nlohmann::json &rootsj = mj["roots"]; + if (rootsj.is_array()) { + for(unsigned long i=0;i<(unsigned long)rootsj.size();++i) { + nlohmann::json &r = rootsj[i]; + if (r.is_object()) { + roots.push_back(World::Root()); + roots.back().identity = Identity(OSUtils::jsonString(r["identity"],"")); + nlohmann::json &stableEndpointsj = r["stableEndpoints"]; + if (stableEndpointsj.is_array()) { + for(unsigned long k=0;k<(unsigned long)stableEndpointsj.size();++k) + roots.back().stableEndpoints.push_back(InetAddress(OSUtils::jsonString(stableEndpointsj[k],""))); + std::sort(roots.back().stableEndpoints.begin(),roots.back().stableEndpoints.end()); + } + } + } + } + std::sort(roots.begin(),roots.end()); + + const uint64_t now = OSUtils::now(); + World w(World::make(t,id,now,updatesMustBeSignedBy,roots,signingKey)); + Buffer wbuf; + w.serialize(wbuf); + char fn[128]; + Utils::snprintf(fn,sizeof(fn),"%.16llx.moon",w.id()); + OSUtils::writeFile(fn,wbuf.data(),wbuf.size()); + printf("wrote %s (signed world with timestamp %llu)" ZT_EOL_S,fn,(unsigned long long)now); + } + } else { + idtoolPrintHelp(stdout,argv[0]); + return 1; + } + + return 0; +} + +/****************************************************************************/ +/* Unix helper functions and signal handlers */ +/****************************************************************************/ + +#ifdef __UNIX_LIKE__ +static void _sighandlerHup(int sig) +{ +} +static void _sighandlerQuit(int sig) +{ + OneService *s = zt1Service; + if (s) + s->terminate(); + else exit(0); +} +#endif + +// Drop privileges on Linux, if supported by libc etc. and "zerotier-one" user exists on system +#ifdef __LINUX__ +#ifndef PR_CAP_AMBIENT +#define PR_CAP_AMBIENT 47 +#define PR_CAP_AMBIENT_IS_SET 1 +#define PR_CAP_AMBIENT_RAISE 2 +#define PR_CAP_AMBIENT_LOWER 3 +#define PR_CAP_AMBIENT_CLEAR_ALL 4 +#endif +#define ZT_LINUX_USER "zerotier-one" +#define ZT_HAVE_DROP_PRIVILEGES 1 +namespace { + +// libc doesn't export capset, it is instead located in libcap +// We ignore libcap and call it manually. +struct cap_header_struct { + __u32 version; + int pid; +}; +struct cap_data_struct { + __u32 effective; + __u32 permitted; + __u32 inheritable; +}; +static inline int _zt_capset(cap_header_struct* hdrp, cap_data_struct* datap) { return syscall(SYS_capset, hdrp, datap); } + +static void _notDropping(const char *procName,const std::string &homeDir) +{ + struct stat buf; + if (lstat(homeDir.c_str(),&buf) < 0) { + if (buf.st_uid != 0 || buf.st_gid != 0) { + fprintf(stderr, "%s: FATAL: failed to drop privileges and can't run as root since privileges were previously dropped (home directory not owned by root)" ZT_EOL_S,procName); + exit(1); + } + } + fprintf(stderr, "%s: WARNING: failed to drop privileges (kernel may not support required prctl features), running as root" ZT_EOL_S,procName); +} + +static int _setCapabilities(int flags) +{ + cap_header_struct capheader = {_LINUX_CAPABILITY_VERSION_1, 0}; + cap_data_struct capdata; + capdata.inheritable = capdata.permitted = capdata.effective = flags; + return _zt_capset(&capheader, &capdata); +} + +static void _recursiveChown(const char *path,uid_t uid,gid_t gid) +{ + struct dirent de; + struct dirent *dptr; + lchown(path,uid,gid); + DIR *d = opendir(path); + if (!d) + return; + dptr = (struct dirent *)0; + for(;;) { + if (readdir_r(d,&de,&dptr) != 0) + break; + if (!dptr) + break; + if ((strcmp(dptr->d_name,".") != 0)&&(strcmp(dptr->d_name,"..") != 0)&&(strlen(dptr->d_name) > 0)) { + std::string p(path); + p.push_back(ZT_PATH_SEPARATOR); + p.append(dptr->d_name); + _recursiveChown(p.c_str(),uid,gid); // will just fail and return on regular files + } + } + closedir(d); +} + +static void dropPrivileges(const char *procName,const std::string &homeDir) +{ + if (getuid() != 0) + return; + + // dropPrivileges switches to zerotier-one user while retaining CAP_NET_ADMIN + // and CAP_NET_RAW capabilities. + struct passwd *targetUser = getpwnam(ZT_LINUX_USER); + if (!targetUser) + return; + + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_NET_RAW, 0, 0) < 0) { + // Kernel has no support for ambient capabilities. + _notDropping(procName,homeDir); + return; + } + if (prctl(PR_SET_SECUREBITS, SECBIT_KEEP_CAPS | SECBIT_NOROOT) < 0) { + _notDropping(procName,homeDir); + return; + } + + // Change ownership of our home directory if everything looks good (does nothing if already chown'd) + _recursiveChown(homeDir.c_str(),targetUser->pw_uid,targetUser->pw_gid); + + if (_setCapabilities((1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID) | (1 << CAP_SETGID)) < 0) { + _notDropping(procName,homeDir); + return; + } + + int oldDumpable = prctl(PR_GET_DUMPABLE); + if (prctl(PR_SET_DUMPABLE, 0) < 0) { + // Disable ptracing. Otherwise there is a small window when previous + // compromised ZeroTier process could ptrace us, when we still have CAP_SETUID. + // (this is mitigated anyway on most distros by ptrace_scope=1) + fprintf(stderr,"%s: FATAL: prctl(PR_SET_DUMPABLE) failed while attempting to relinquish root permissions" ZT_EOL_S,procName); + exit(1); + } + + // Relinquish root + if (setgid(targetUser->pw_gid) < 0) { + perror("setgid"); + exit(1); + } + if (setuid(targetUser->pw_uid) < 0) { + perror("setuid"); + exit(1); + } + + if (_setCapabilities((1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW)) < 0) { + fprintf(stderr,"%s: FATAL: unable to drop capabilities after relinquishing root" ZT_EOL_S,procName); + exit(1); + } + + if (prctl(PR_SET_DUMPABLE, oldDumpable) < 0) { + fprintf(stderr,"%s: FATAL: prctl(PR_SET_DUMPABLE) failed while attempting to relinquish root permissions" ZT_EOL_S,procName); + exit(1); + } + + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_ADMIN, 0, 0) < 0) { + fprintf(stderr,"%s: FATAL: prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_RAISE,CAP_NET_ADMIN) failed while attempting to relinquish root permissions" ZT_EOL_S,procName); + exit(1); + } + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_RAW, 0, 0) < 0) { + fprintf(stderr,"%s: FATAL: prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_RAISE,CAP_NET_RAW) failed while attempting to relinquish root permissions" ZT_EOL_S,procName); + exit(1); + } +} + +} // anonymous namespace +#endif // __LINUX__ + +/****************************************************************************/ +/* Windows helper functions and signal handlers */ +/****************************************************************************/ + +#ifdef __WINDOWS__ +// Console signal handler routine to allow CTRL+C to work, mostly for testing +static BOOL WINAPI _winConsoleCtrlHandler(DWORD dwCtrlType) +{ + switch(dwCtrlType) { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_SHUTDOWN_EVENT: + OneService *s = zt1Service; + if (s) + s->terminate(); + return TRUE; + } + return FALSE; +} + +static void _winPokeAHole() +{ + char myPath[MAX_PATH]; + DWORD ps = GetModuleFileNameA(NULL,myPath,sizeof(myPath)); + if ((ps > 0)&&(ps < (DWORD)sizeof(myPath))) { + STARTUPINFOA startupInfo; + PROCESS_INFORMATION processInfo; + + startupInfo.cb = sizeof(startupInfo); + memset(&startupInfo,0,sizeof(STARTUPINFOA)); + memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); + if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall delete rule name=\"ZeroTier One\" program=\"") + myPath + "\"").c_str(),NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&startupInfo,&processInfo)) { + WaitForSingleObject(processInfo.hProcess,INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + + startupInfo.cb = sizeof(startupInfo); + memset(&startupInfo,0,sizeof(STARTUPINFOA)); + memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); + if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall add rule name=\"ZeroTier One\" dir=in action=allow program=\"") + myPath + "\" enable=yes").c_str(),NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&startupInfo,&processInfo)) { + WaitForSingleObject(processInfo.hProcess,INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + + startupInfo.cb = sizeof(startupInfo); + memset(&startupInfo,0,sizeof(STARTUPINFOA)); + memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); + if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall add rule name=\"ZeroTier One\" dir=out action=allow program=\"") + myPath + "\" enable=yes").c_str(),NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&startupInfo,&processInfo)) { + WaitForSingleObject(processInfo.hProcess,INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + } +} + +// Returns true if this is running as the local administrator +static BOOL IsCurrentUserLocalAdministrator(void) +{ + BOOL fReturn = FALSE; + DWORD dwStatus; + DWORD dwAccessMask; + DWORD dwAccessDesired; + DWORD dwACLSize; + DWORD dwStructureSize = sizeof(PRIVILEGE_SET); + PACL pACL = NULL; + PSID psidAdmin = NULL; + + HANDLE hToken = NULL; + HANDLE hImpersonationToken = NULL; + + PRIVILEGE_SET ps; + GENERIC_MAPPING GenericMapping; + + PSECURITY_DESCRIPTOR psdAdmin = NULL; + SID_IDENTIFIER_AUTHORITY SystemSidAuthority = SECURITY_NT_AUTHORITY; + + const DWORD ACCESS_READ = 1; + const DWORD ACCESS_WRITE = 2; + + __try + { + if (!OpenThreadToken(GetCurrentThread(), TOKEN_DUPLICATE|TOKEN_QUERY,TRUE,&hToken)) + { + if (GetLastError() != ERROR_NO_TOKEN) + __leave; + if (!OpenProcessToken(GetCurrentProcess(),TOKEN_DUPLICATE|TOKEN_QUERY, &hToken)) + __leave; + } + if (!DuplicateToken (hToken, SecurityImpersonation,&hImpersonationToken)) + __leave; + if (!AllocateAndInitializeSid(&SystemSidAuthority, 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, &psidAdmin)) + __leave; + psdAdmin = LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + if (psdAdmin == NULL) + __leave; + if (!InitializeSecurityDescriptor(psdAdmin,SECURITY_DESCRIPTOR_REVISION)) + __leave; + dwACLSize = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(psidAdmin) - sizeof(DWORD); + pACL = (PACL)LocalAlloc(LPTR, dwACLSize); + if (pACL == NULL) + __leave; + if (!InitializeAcl(pACL, dwACLSize, ACL_REVISION2)) + __leave; + dwAccessMask= ACCESS_READ | ACCESS_WRITE; + if (!AddAccessAllowedAce(pACL, ACL_REVISION2, dwAccessMask, psidAdmin)) + __leave; + if (!SetSecurityDescriptorDacl(psdAdmin, TRUE, pACL, FALSE)) + __leave; + + SetSecurityDescriptorGroup(psdAdmin, psidAdmin, FALSE); + SetSecurityDescriptorOwner(psdAdmin, psidAdmin, FALSE); + + if (!IsValidSecurityDescriptor(psdAdmin)) + __leave; + dwAccessDesired = ACCESS_READ; + + GenericMapping.GenericRead = ACCESS_READ; + GenericMapping.GenericWrite = ACCESS_WRITE; + GenericMapping.GenericExecute = 0; + GenericMapping.GenericAll = ACCESS_READ | ACCESS_WRITE; + + if (!AccessCheck(psdAdmin, hImpersonationToken, dwAccessDesired, + &GenericMapping, &ps, &dwStructureSize, &dwStatus, + &fReturn)) + { + fReturn = FALSE; + __leave; + } + } + __finally + { + // Clean up. + if (pACL) LocalFree(pACL); + if (psdAdmin) LocalFree(psdAdmin); + if (psidAdmin) FreeSid(psidAdmin); + if (hImpersonationToken) CloseHandle (hImpersonationToken); + if (hToken) CloseHandle (hToken); + } + + return fReturn; +} +#endif // __WINDOWS__ + +/****************************************************************************/ +/* main() and friends */ +/****************************************************************************/ + +static void printHelp(const char *cn,FILE *out) +{ + fprintf(out, + "%s version %d.%d.%d" ZT_EOL_S, + PROGRAM_NAME, + ZEROTIER_ONE_VERSION_MAJOR, ZEROTIER_ONE_VERSION_MINOR, ZEROTIER_ONE_VERSION_REVISION); + fprintf(out, + COPYRIGHT_NOTICE ZT_EOL_S + LICENSE_GRANT ZT_EOL_S); + fprintf(out,"Usage: %s [-switches] [home directory]" ZT_EOL_S"" ZT_EOL_S,cn); + fprintf(out,"Available switches:" ZT_EOL_S); + fprintf(out," -h - Display this help" ZT_EOL_S); + fprintf(out," -v - Show version" ZT_EOL_S); + fprintf(out," -U - Skip privilege check and do not attempt to drop privileges" ZT_EOL_S); + fprintf(out," -p - Port for UDP and TCP/HTTP (default: 9993, 0 for random)" ZT_EOL_S); + +#ifdef __UNIX_LIKE__ + fprintf(out," -d - Fork and run as daemon (Unix-ish OSes)" ZT_EOL_S); +#endif // __UNIX_LIKE__ + +#ifdef __WINDOWS__ + fprintf(out," -C - Run from command line instead of as service (Windows)" ZT_EOL_S); + fprintf(out," -I - Install Windows service (Windows)" ZT_EOL_S); + fprintf(out," -R - Uninstall Windows service (Windows)" ZT_EOL_S); + fprintf(out," -D - Remove all instances of Windows tap device (Windows)" ZT_EOL_S); +#endif // __WINDOWS__ + + fprintf(out," -i - Generate and manage identities (zerotier-idtool)" ZT_EOL_S); + fprintf(out," -q - Query API (zerotier-cli)" ZT_EOL_S); +} + +class _OneServiceRunner +{ +public: + _OneServiceRunner(const char *pn,const std::string &hd,unsigned int p) : progname(pn),returnValue(0),port(p),homeDir(hd) {} + void threadMain() + throw() + { + try { + for(;;) { + zt1Service = OneService::newInstance(homeDir.c_str(),port); + switch(zt1Service->run()) { + case OneService::ONE_STILL_RUNNING: // shouldn't happen, run() won't return until done + case OneService::ONE_NORMAL_TERMINATION: + break; + case OneService::ONE_UNRECOVERABLE_ERROR: + fprintf(stderr,"%s: fatal error: %s" ZT_EOL_S,progname,zt1Service->fatalErrorMessage().c_str()); + returnValue = 1; + break; + case OneService::ONE_IDENTITY_COLLISION: { + delete zt1Service; + zt1Service = (OneService *)0; + std::string oldid; + OSUtils::readFile((homeDir + ZT_PATH_SEPARATOR_S + "identity.secret").c_str(),oldid); + if (oldid.length()) { + OSUtils::writeFile((homeDir + ZT_PATH_SEPARATOR_S + "identity.secret.saved_after_collision").c_str(),oldid); + OSUtils::rm((homeDir + ZT_PATH_SEPARATOR_S + "identity.secret").c_str()); + OSUtils::rm((homeDir + ZT_PATH_SEPARATOR_S + "identity.public").c_str()); + } + } continue; // restart! + } + break; // terminate loop -- normally we don't keep restarting + } + + delete zt1Service; + zt1Service = (OneService *)0; + } catch ( ... ) { + fprintf(stderr,"%s: unexpected exception starting main OneService instance" ZT_EOL_S,progname); + returnValue = 1; + } + } + const char *progname; + unsigned int returnValue; + unsigned int port; + const std::string &homeDir; +}; + +#ifdef __WINDOWS__ +int __cdecl _tmain(int argc, _TCHAR* argv[]) +#else +int main(int argc,char **argv) +#endif +{ +#ifdef __UNIX_LIKE__ + signal(SIGHUP,&_sighandlerHup); + signal(SIGPIPE,SIG_IGN); + signal(SIGUSR1,SIG_IGN); + signal(SIGUSR2,SIG_IGN); + signal(SIGALRM,SIG_IGN); + signal(SIGINT,&_sighandlerQuit); + signal(SIGTERM,&_sighandlerQuit); + signal(SIGQUIT,&_sighandlerQuit); + + /* Ensure that there are no inherited file descriptors open from a previous + * incarnation. This is a hack to ensure that GitHub issue #61 or variants + * of it do not return, and should not do anything otherwise bad. */ + { + int mfd = STDIN_FILENO; + if (STDOUT_FILENO > mfd) mfd = STDOUT_FILENO; + if (STDERR_FILENO > mfd) mfd = STDERR_FILENO; + for(int f=mfd+1;f<1024;++f) + ::close(f); + } + + bool runAsDaemon = false; +#endif // __UNIX_LIKE__ + +#ifdef __WINDOWS__ + { + WSADATA wsaData; + WSAStartup(MAKEWORD(2,2),&wsaData); + } + +#ifdef ZT_WIN_RUN_IN_CONSOLE + bool winRunFromCommandLine = true; +#else + bool winRunFromCommandLine = false; +#endif +#endif // __WINDOWS__ + + if ((strstr(argv[0],"zerotier-idtool"))||(strstr(argv[0],"ZEROTIER-IDTOOL"))) + return idtool(argc,argv); + if ((strstr(argv[0],"zerotier-cli"))||(strstr(argv[0],"ZEROTIER-CLI"))) + return cli(argc,argv); + + std::string homeDir; + unsigned int port = ZT_DEFAULT_PORT; + bool skipRootCheck = false; + + for(int i=1;i 0xffff) { + printHelp(argv[0],stdout); + return 1; + } + break; + +#ifdef __UNIX_LIKE__ + case 'd': // Run in background as daemon + runAsDaemon = true; + break; +#endif // __UNIX_LIKE__ + + case 'U': + skipRootCheck = true; + break; + + case 'v': // Display version + printf("%d.%d.%d" ZT_EOL_S,ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION); + return 0; + + case 'i': // Invoke idtool personality + if (argv[i][2]) { + printHelp(argv[0],stdout); + return 0; + } else return idtool(argc,argv); + + case 'q': // Invoke cli personality + if (argv[i][2]) { + printHelp(argv[0],stdout); + return 0; + } else return cli(argc,argv); + +#ifdef __WINDOWS__ + case 'C': // Run from command line instead of as Windows service + winRunFromCommandLine = true; + break; + + case 'I': { // Install this binary as a Windows service + if (IsCurrentUserLocalAdministrator() != TRUE) { + fprintf(stderr,"%s: must be run as a local administrator." ZT_EOL_S,argv[0]); + return 1; + } + std::string ret(InstallService(ZT_SERVICE_NAME,ZT_SERVICE_DISPLAY_NAME,ZT_SERVICE_START_TYPE,ZT_SERVICE_DEPENDENCIES,ZT_SERVICE_ACCOUNT,ZT_SERVICE_PASSWORD)); + if (ret.length()) { + fprintf(stderr,"%s: unable to install service: %s" ZT_EOL_S,argv[0],ret.c_str()); + return 3; + } + return 0; + } break; + + case 'R': { // Uninstall this binary as Windows service + if (IsCurrentUserLocalAdministrator() != TRUE) { + fprintf(stderr,"%s: must be run as a local administrator." ZT_EOL_S,argv[0]); + return 1; + } + std::string ret(UninstallService(ZT_SERVICE_NAME)); + if (ret.length()) { + fprintf(stderr,"%s: unable to uninstall service: %s" ZT_EOL_S,argv[0],ret.c_str()); + return 3; + } + return 0; + } break; + + case 'D': { + std::string err = WindowsEthernetTap::destroyAllPersistentTapDevices(); + if (err.length() > 0) { + fprintf(stderr,"%s: unable to uninstall one or more persistent tap devices: %s" ZT_EOL_S,argv[0],err.c_str()); + return 3; + } + return 0; + } break; +#endif // __WINDOWS__ + + case 'h': + case '?': + default: + printHelp(argv[0],stdout); + return 0; + } + } else { + if (homeDir.length()) { + printHelp(argv[0],stdout); + return 0; + } else { + homeDir = argv[i]; + } + } + } + + if (!homeDir.length()) + homeDir = OneService::platformDefaultHomePath(); + if (!homeDir.length()) { + fprintf(stderr,"%s: no home path specified and no platform default available" ZT_EOL_S,argv[0]); + return 1; + } else { + std::vector hpsp(OSUtils::split(homeDir.c_str(),ZT_PATH_SEPARATOR_S,"","")); + std::string ptmp; + if (homeDir[0] == ZT_PATH_SEPARATOR) + ptmp.push_back(ZT_PATH_SEPARATOR); + for(std::vector::iterator pi(hpsp.begin());pi!=hpsp.end();++pi) { + if (ptmp.length() > 0) + ptmp.push_back(ZT_PATH_SEPARATOR); + ptmp.append(*pi); + if ((*pi != ".")&&(*pi != "..")) { + if (!OSUtils::mkdir(ptmp)) + throw std::runtime_error("home path does not exist, and could not create"); + } + } + } + + // This can be removed once the new controller code has been around for many versions + if (OSUtils::fileExists((homeDir + ZT_PATH_SEPARATOR_S + "controller.db").c_str(),true)) { + fprintf(stderr,"%s: FATAL: an old controller.db exists in %s -- see instructions in controller/README.md for how to migrate!" ZT_EOL_S,argv[0],homeDir.c_str()); + return 1; + } + +#ifdef __UNIX_LIKE__ +#ifndef ZT_ONE_NO_ROOT_CHECK + if ((!skipRootCheck)&&(getuid() != 0)) { + fprintf(stderr,"%s: must be run as root (uid 0)" ZT_EOL_S,argv[0]); + return 1; + } +#endif // !ZT_ONE_NO_ROOT_CHECK + if (runAsDaemon) { + long p = (long)fork(); + if (p < 0) { + fprintf(stderr,"%s: could not fork" ZT_EOL_S,argv[0]); + return 1; + } else if (p > 0) + return 0; // forked + // else p == 0, so we are daemonized + } +#endif // __UNIX_LIKE__ + +#ifdef __WINDOWS__ + // Uninstall legacy tap devices. New devices will automatically be installed and configured + // when tap instances are created. + WindowsEthernetTap::destroyAllLegacyPersistentTapDevices(); + + if (winRunFromCommandLine) { + // Running in "interactive" mode (mostly for debugging) + if (IsCurrentUserLocalAdministrator() != TRUE) { + if (!skipRootCheck) { + fprintf(stderr,"%s: must be run as a local administrator." ZT_EOL_S,argv[0]); + return 1; + } + } else { + _winPokeAHole(); + } + SetConsoleCtrlHandler(&_winConsoleCtrlHandler,TRUE); + // continues on to ordinary command line execution code below... + } else { + // Running from service manager + _winPokeAHole(); + ZeroTierOneService zt1WindowsService; + if (CServiceBase::Run(zt1WindowsService) == TRUE) { + return 0; + } else { + fprintf(stderr,"%s: unable to start service (try -h for help)" ZT_EOL_S,argv[0]); + return 1; + } + } +#endif // __WINDOWS__ + +#ifdef __UNIX_LIKE__ +#ifdef ZT_HAVE_DROP_PRIVILEGES + dropPrivileges(argv[0],homeDir); +#endif + + std::string pidPath(homeDir + ZT_PATH_SEPARATOR_S + ZT_PID_PATH); + { + // Write .pid file to home folder + FILE *pf = fopen(pidPath.c_str(),"w"); + if (pf) { + fprintf(pf,"%ld",(long)getpid()); + fclose(pf); + } + } +#endif // __UNIX_LIKE__ + + _OneServiceRunner thr(argv[0],homeDir,port); + thr.threadMain(); + //Thread::join(Thread::start(&thr)); + +#ifdef __UNIX_LIKE__ + OSUtils::rm(pidPath.c_str()); +#endif + + return thr.returnValue; +} diff --git a/osdep/Arp.cpp b/osdep/Arp.cpp new file mode 100644 index 0000000..fcc122f --- /dev/null +++ b/osdep/Arp.cpp @@ -0,0 +1,126 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include + +#include "Arp.hpp" +#include "OSUtils.hpp" + +namespace ZeroTier { + +static const uint8_t ARP_REQUEST_HEADER[8] = { 0x00,0x01,0x08,0x00,0x06,0x04,0x00,0x01 }; +static const uint8_t ARP_RESPONSE_HEADER[8] = { 0x00,0x01,0x08,0x00,0x06,0x04,0x00,0x02 }; + +Arp::Arp() : + _cache(256), + _lastCleaned(OSUtils::now()) +{ +} + +void Arp::addLocal(uint32_t ip,const MAC &mac) +{ + _ArpEntry &e = _cache[ip]; + e.lastQuerySent = 0; // local IP + e.lastResponseReceived = 0; // local IP + e.mac = mac; + e.local = true; +} + +void Arp::remove(uint32_t ip) +{ + _cache.erase(ip); +} + +uint32_t Arp::processIncomingArp(const void *arp,unsigned int len,void *response,unsigned int &responseLen,MAC &responseDest) +{ + const uint64_t now = OSUtils::now(); + uint32_t ip = 0; + + responseLen = 0; + responseDest.zero(); + + if (len >= 28) { + if (!memcmp(arp,ARP_REQUEST_HEADER,8)) { + // Respond to ARP requests for locally-known IPs + _ArpEntry *targetEntry = _cache.get(reinterpret_cast(arp)[6]); + if ((targetEntry)&&(targetEntry->local)) { + memcpy(response,ARP_RESPONSE_HEADER,8); + targetEntry->mac.copyTo(reinterpret_cast(response) + 8,6); + memcpy(reinterpret_cast(response) + 14,reinterpret_cast(arp) + 24,4); + memcpy(reinterpret_cast(response) + 18,reinterpret_cast(arp) + 8,10); + responseLen = 28; + responseDest.setTo(reinterpret_cast(arp) + 8,6); + } + } else if (!memcmp(arp,ARP_RESPONSE_HEADER,8)) { + // Learn cache entries for remote IPs from relevant ARP replies + uint32_t responseIp = 0; + memcpy(&responseIp,reinterpret_cast(arp) + 14,4); + _ArpEntry *queryEntry = _cache.get(responseIp); + if ((queryEntry)&&(!queryEntry->local)&&((now - queryEntry->lastQuerySent) <= ZT_ARP_QUERY_MAX_TTL)) { + queryEntry->lastResponseReceived = now; + queryEntry->mac.setTo(reinterpret_cast(arp) + 8,6); + ip = responseIp; + } + } + } + + if ((now - _lastCleaned) >= ZT_ARP_EXPIRE) { + _lastCleaned = now; + Hashtable< uint32_t,_ArpEntry >::Iterator i(_cache); + uint32_t *k = (uint32_t *)0; + _ArpEntry *v = (_ArpEntry *)0; + while (i.next(k,v)) { + if ((!v->local)&&((now - v->lastResponseReceived) >= ZT_ARP_EXPIRE)) + _cache.erase(*k); + } + } + + return ip; +} + +MAC Arp::query(const MAC &localMac,uint32_t localIp,uint32_t targetIp,void *query,unsigned int &queryLen,MAC &queryDest) +{ + const uint64_t now = OSUtils::now(); + + _ArpEntry &e = _cache[targetIp]; + + if ( ((e.mac)&&((now - e.lastResponseReceived) >= (ZT_ARP_EXPIRE / 3))) || + ((!e.mac)&&((now - e.lastQuerySent) >= ZT_ARP_QUERY_INTERVAL)) ) { + e.lastQuerySent = now; + + uint8_t *q = reinterpret_cast(query); + memcpy(q,ARP_REQUEST_HEADER,8); q += 8; // ARP request header information, always the same + localMac.copyTo(q,6); q += 6; // sending host MAC address + memcpy(q,&localIp,4); q += 4; // sending host IP (IP already in big-endian byte order) + memset(q,0,6); q += 6; // sending zeros for target MAC address as thats what we want to find + memcpy(q,&targetIp,4); // target IP address for resolution (IP already in big-endian byte order) + queryLen = 28; + if (e.mac) + queryDest = e.mac; // confirmation query, send directly to address holder + else queryDest = (uint64_t)0xffffffffffffULL; // broadcast query + } else { + queryLen = 0; + queryDest.zero(); + } + + return e.mac; +} + +} // namespace ZeroTier diff --git a/osdep/Arp.hpp b/osdep/Arp.hpp new file mode 100644 index 0000000..5f0d199 --- /dev/null +++ b/osdep/Arp.hpp @@ -0,0 +1,148 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_ARP_HPP +#define ZT_ARP_HPP + +#include + +#include + +#include "../node/Constants.hpp" +#include "../node/Hashtable.hpp" +#include "../node/MAC.hpp" + +/** + * Maximum possible ARP length + * + * ARPs are 28 bytes in length, but specify a 128 byte buffer since + * some weird extensions we may support in the future can pad them + * out to as long as 72 bytes. + */ +#define ZT_ARP_BUF_LENGTH 128 + +/** + * Minimum permitted interval between sending ARP queries for a given IP + */ +#define ZT_ARP_QUERY_INTERVAL 2000 + +/** + * Maximum time between query and response, otherwise responses are discarded to prevent poisoning + */ +#define ZT_ARP_QUERY_MAX_TTL 5000 + +/** + * ARP expiration time + */ +#define ZT_ARP_EXPIRE 600000 + +namespace ZeroTier { + +/** + * ARP cache and resolver + * + * To implement ARP: + * + * (1) Call processIncomingArp() on all ARP packets received and then always + * check responseLen after calling. If it is non-zero, send the contents + * of response to responseDest. + * + * (2) Call query() to look up IP addresses, and then check queryLen. If it + * is non-zero, send the contents of query to queryDest (usually broadcast). + * + * Note that either of these functions can technically generate a response or + * a query at any time, so their result parameters for sending ARPs should + * always be checked. + * + * This class is not thread-safe and must be guarded if used in multi-threaded + * code. + */ +class Arp +{ +public: + Arp(); + + /** + * Set a local IP entry that we should respond to ARPs for + * + * @param mac Our local MAC address + * @param ip IP in big-endian byte order (sin_addr.s_addr) + */ + void addLocal(uint32_t ip,const MAC &mac); + + /** + * Delete a local IP entry or a cached ARP entry + * + * @param ip IP in big-endian byte order (sin_addr.s_addr) + */ + void remove(uint32_t ip); + + /** + * Process ARP packets + * + * For ARP queries, a response is generated and responseLen is set to its + * frame payload length in bytes. + * + * For ARP responses, the cache is populated and the IP address entry that + * was learned is returned. + * + * @param arp ARP frame data + * @param len Length of ARP frame (usually 28) + * @param response Response buffer -- MUST be a minimum of ZT_ARP_BUF_LENGTH in size + * @param responseLen Response length, or set to 0 if no response + * @param responseDest Destination of response, or set to null if no response + * @return IP address learned or 0 if no new IPs in cache + */ + uint32_t processIncomingArp(const void *arp,unsigned int len,void *response,unsigned int &responseLen,MAC &responseDest); + + /** + * Get the MAC corresponding to an IP, generating a query if needed + * + * This returns a MAC for a remote IP. The local MAC is returned for local + * IPs as well. It may also generate a query if the IP is not known or the + * entry needs to be refreshed. In this case queryLen will be set to a + * non-zero value, so this should always be checked on return even if the + * MAC returned is non-null. + * + * @param localMac Local MAC address of host interface + * @param localIp Local IP address of host interface + * @param targetIp IP to look up + * @param query Buffer for generated query -- MUST be a minimum of ZT_ARP_BUF_LENGTH in size + * @param queryLen Length of generated query, or set to 0 if no query generated + * @param queryDest Destination of query, or set to null if no query generated + * @return MAC or 0 if no cached entry for this IP + */ + MAC query(const MAC &localMac,uint32_t localIp,uint32_t targetIp,void *query,unsigned int &queryLen,MAC &queryDest); + +private: + struct _ArpEntry + { + _ArpEntry() : lastQuerySent(0),lastResponseReceived(0),mac(),local(false) {} + uint64_t lastQuerySent; // Time last query was sent or 0 for local IP + uint64_t lastResponseReceived; // Time of last ARP response or 0 for local IP + MAC mac; // MAC address of device responsible for IP or null if not known yet + bool local; // True if this is a local ARP entry + }; + + Hashtable< uint32_t,_ArpEntry > _cache; + uint64_t _lastCleaned; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/BSDEthernetTap.cpp b/osdep/BSDEthernetTap.cpp new file mode 100644 index 0000000..62fabc4 --- /dev/null +++ b/osdep/BSDEthernetTap.cpp @@ -0,0 +1,473 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" +#include "../node/Mutex.hpp" +#include "OSUtils.hpp" +#include "BSDEthernetTap.hpp" + +#define ZT_BASE32_CHARS "0123456789abcdefghijklmnopqrstuv" + +// ff:ff:ff:ff:ff:ff with no ADI +static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); + +namespace ZeroTier { + +BSDEthernetTap::BSDEthernetTap( + const char *homePath, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *friendlyName, + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void *arg) : + _handler(handler), + _arg(arg), + _nwid(nwid), + _mtu(mtu), + _metric(metric), + _fd(0), + _enabled(true) +{ + static Mutex globalTapCreateLock; + char devpath[64],ethaddr[64],mtustr[32],metstr[32],tmpdevname[32]; + + Mutex::Lock _gl(globalTapCreateLock); + + if (mtu > 2800) + throw std::runtime_error("max tap MTU is 2800"); + +#ifdef __FreeBSD__ + /* FreeBSD allows long interface names and interface renaming */ + + _dev = "zt"; + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 60) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 55) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 50) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 45) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 40) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 35) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 30) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 25) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 20) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 15) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 10) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)((nwid >> 5) & 0x1f)]); + _dev.push_back(ZT_BASE32_CHARS[(unsigned long)(nwid & 0x1f)]); + + std::vector devFiles(OSUtils::listDirectory("/dev")); + for(int i=9993;i<(9993+128);++i) { + Utils::snprintf(tmpdevname,sizeof(tmpdevname),"tap%d",i); + Utils::snprintf(devpath,sizeof(devpath),"/dev/%s",tmpdevname); + if (std::find(devFiles.begin(),devFiles.end(),std::string(tmpdevname)) == devFiles.end()) { + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",tmpdevname,"create",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + } else throw std::runtime_error("fork() failed"); + + struct stat stattmp; + if (!stat(devpath,&stattmp)) { + cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",tmpdevname,"name",_dev.c_str(),(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + if (exitcode) + throw std::runtime_error("ifconfig rename operation failed"); + } else throw std::runtime_error("fork() failed"); + + _fd = ::open(devpath,O_RDWR); + if (_fd > 0) + break; + else throw std::runtime_error("unable to open created tap device"); + } else { + throw std::runtime_error("cannot find /dev node for newly created tap device"); + } + } + } +#else + /* Other BSDs like OpenBSD only have a limited number of tap devices that cannot be renamed */ + + for(int i=0;i<64;++i) { + Utils::snprintf(tmpdevname,sizeof(tmpdevname),"tap%d",i); + Utils::snprintf(devpath,sizeof(devpath),"/dev/%s",tmpdevname); + _fd = ::open(devpath,O_RDWR); + if (_fd > 0) { + _dev = tmpdevname; + break; + } + } +#endif + + if (_fd <= 0) + throw std::runtime_error("unable to open TAP device or no more devices available"); + + if (fcntl(_fd,F_SETFL,fcntl(_fd,F_GETFL) & ~O_NONBLOCK) == -1) { + ::close(_fd); + throw std::runtime_error("unable to set flags on file descriptor for TAP device"); + } + + // Configure MAC address and MTU, bring interface up + Utils::snprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]); + Utils::snprintf(mtustr,sizeof(mtustr),"%u",_mtu); + Utils::snprintf(metstr,sizeof(metstr),"%u",_metric); + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + if (exitcode) { + ::close(_fd); + throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface"); + } + } + + // Set close-on-exec so that devices cannot persist if we fork/exec for update + fcntl(_fd,F_SETFD,fcntl(_fd,F_GETFD) | FD_CLOEXEC); + + ::pipe(_shutdownSignalPipe); + + _thread = Thread::start(this); +} + +BSDEthernetTap::~BSDEthernetTap() +{ + ::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit + Thread::join(_thread); + ::close(_fd); + ::close(_shutdownSignalPipe[0]); + ::close(_shutdownSignalPipe[1]); + + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"destroy",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + } +} + +void BSDEthernetTap::setEnabled(bool en) +{ + _enabled = en; +} + +bool BSDEthernetTap::enabled() const +{ + return _enabled; +} + +static bool ___removeIp(const std::string &_dev,const InetAddress &ip) +{ + long cpid = (long)vfork(); + if (cpid == 0) { + execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"inet",ip.toIpString().c_str(),"-alias",(const char *)0); + _exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + return false; // never reached, make compiler shut up about return value +} + +bool BSDEthernetTap::addIp(const InetAddress &ip) +{ + if (!ip) + return false; + + std::vector allIps(ips()); + if (std::find(allIps.begin(),allIps.end(),ip) != allIps.end()) + return true; // IP/netmask already assigned + + // Remove and reconfigure if address is the same but netmask is different + for(std::vector::iterator i(allIps.begin());i!=allIps.end();++i) { + if ((i->ipsEqual(ip))&&(i->netmaskBits() != ip.netmaskBits())) { + if (___removeIp(_dev,*i)) + break; + } + } + + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),ip.isV4() ? "inet" : "inet6",ip.toString().c_str(),"alias",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + return false; +} + +bool BSDEthernetTap::removeIp(const InetAddress &ip) +{ + if (!ip) + return false; + std::vector allIps(ips()); + if (std::find(allIps.begin(),allIps.end(),ip) != allIps.end()) { + if (___removeIp(_dev,ip)) + return true; + } + return false; +} + +std::vector BSDEthernetTap::ips() const +{ + struct ifaddrs *ifa = (struct ifaddrs *)0; + if (getifaddrs(&ifa)) + return std::vector(); + + std::vector r; + + struct ifaddrs *p = ifa; + while (p) { + if ((!strcmp(p->ifa_name,_dev.c_str()))&&(p->ifa_addr)&&(p->ifa_netmask)&&(p->ifa_addr->sa_family == p->ifa_netmask->sa_family)) { + switch(p->ifa_addr->sa_family) { + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *)p->ifa_addr; + struct sockaddr_in *nm = (struct sockaddr_in *)p->ifa_netmask; + r.push_back(InetAddress(&(sin->sin_addr.s_addr),4,Utils::countBits((uint32_t)nm->sin_addr.s_addr))); + } break; + case AF_INET6: { + struct sockaddr_in6 *sin = (struct sockaddr_in6 *)p->ifa_addr; + struct sockaddr_in6 *nm = (struct sockaddr_in6 *)p->ifa_netmask; + uint32_t b[4]; + memcpy(b,nm->sin6_addr.s6_addr,sizeof(b)); + r.push_back(InetAddress(sin->sin6_addr.s6_addr,16,Utils::countBits(b[0]) + Utils::countBits(b[1]) + Utils::countBits(b[2]) + Utils::countBits(b[3]))); + } break; + } + } + p = p->ifa_next; + } + + if (ifa) + freeifaddrs(ifa); + + std::sort(r.begin(),r.end()); + std::unique(r.begin(),r.end()); + + return r; +} + +void BSDEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + char putBuf[4096]; + if ((_fd > 0)&&(len <= _mtu)&&(_enabled)) { + to.copyTo(putBuf,6); + from.copyTo(putBuf + 6,6); + *((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType); + memcpy(putBuf + 14,data,len); + len += 14; + ::write(_fd,putBuf,len); + } +} + +std::string BSDEthernetTap::deviceName() const +{ + return _dev; +} + +void BSDEthernetTap::setFriendlyName(const char *friendlyName) +{ +} + +void BSDEthernetTap::scanMulticastGroups(std::vector &added,std::vector &removed) +{ + std::vector newGroups; + +#ifndef __OpenBSD__ + struct ifmaddrs *ifmap = (struct ifmaddrs *)0; + if (!getifmaddrs(&ifmap)) { + struct ifmaddrs *p = ifmap; + while (p) { + if (p->ifma_addr->sa_family == AF_LINK) { + struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name; + struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr; + if ((la->sdl_alen == 6)&&(in->sdl_nlen <= _dev.length())&&(!memcmp(_dev.data(),in->sdl_data,in->sdl_nlen))) + newGroups.push_back(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen,6),0)); + } + p = p->ifma_next; + } + freeifmaddrs(ifmap); + } +#endif // __OpenBSD__ + + std::vector allIps(ips()); + for(std::vector::iterator ip(allIps.begin());ip!=allIps.end();++ip) + newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip)); + + std::sort(newGroups.begin(),newGroups.end()); + std::unique(newGroups.begin(),newGroups.end()); + + for(std::vector::iterator m(newGroups.begin());m!=newGroups.end();++m) { + if (!std::binary_search(_multicastGroups.begin(),_multicastGroups.end(),*m)) + added.push_back(*m); + } + for(std::vector::iterator m(_multicastGroups.begin());m!=_multicastGroups.end();++m) { + if (!std::binary_search(newGroups.begin(),newGroups.end(),*m)) + removed.push_back(*m); + } + + _multicastGroups.swap(newGroups); +} + +/* +bool BSDEthernetTap::updateMulticastGroups(std::set &groups) +{ + std::set newGroups; + struct ifmaddrs *ifmap = (struct ifmaddrs *)0; + if (!getifmaddrs(&ifmap)) { + struct ifmaddrs *p = ifmap; + while (p) { + if (p->ifma_addr->sa_family == AF_LINK) { + struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name; + struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr; + if ((la->sdl_alen == 6)&&(in->sdl_nlen <= _dev.length())&&(!memcmp(_dev.data(),in->sdl_data,in->sdl_nlen))) + newGroups.insert(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen,6),0)); + } + p = p->ifma_next; + } + freeifmaddrs(ifmap); + } + + { + std::set allIps(ips()); + for(std::set::const_iterator i(allIps.begin());i!=allIps.end();++i) + newGroups.insert(MulticastGroup::deriveMulticastGroupForAddressResolution(*i)); + } + + bool changed = false; + + for(std::set::iterator mg(newGroups.begin());mg!=newGroups.end();++mg) { + if (!groups.count(*mg)) { + groups.insert(*mg); + changed = true; + } + } + for(std::set::iterator mg(groups.begin());mg!=groups.end();) { + if ((!newGroups.count(*mg))&&(*mg != _blindWildcardMulticastGroup)) { + groups.erase(mg++); + changed = true; + } else ++mg; + } + + return changed; +} +*/ + +void BSDEthernetTap::threadMain() + throw() +{ + fd_set readfds,nullfds; + MAC to,from; + int n,nfds,r; + char getBuf[8194]; + + // Wait for a moment after startup -- wait for Network to finish + // constructing itself. + Thread::sleep(500); + + FD_ZERO(&readfds); + FD_ZERO(&nullfds); + nfds = (int)std::max(_shutdownSignalPipe[0],_fd) + 1; + + r = 0; + for(;;) { + FD_SET(_shutdownSignalPipe[0],&readfds); + FD_SET(_fd,&readfds); + select(nfds,&readfds,&nullfds,&nullfds,(struct timeval *)0); + + if (FD_ISSET(_shutdownSignalPipe[0],&readfds)) // writes to shutdown pipe terminate thread + break; + + if (FD_ISSET(_fd,&readfds)) { + n = (int)::read(_fd,getBuf + r,sizeof(getBuf) - r); + if (n < 0) { + if ((errno != EINTR)&&(errno != ETIMEDOUT)) + break; + } else { + // Some tap drivers like to send the ethernet frame and the + // payload in two chunks, so handle that by accumulating + // data until we have at least a frame. + r += n; + if (r > 14) { + if (r > ((int)_mtu + 14)) // sanity check for weird TAP behavior on some platforms + r = _mtu + 14; + + if (_enabled) { + to.setTo(getBuf,6); + from.setTo(getBuf + 6,6); + unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); + _handler(_arg,(void *)0,_nwid,from,to,etherType,0,(const void *)(getBuf + 14),r - 14); + } + + r = 0; + } + } + } + } +} + +} // namespace ZeroTier diff --git a/osdep/BSDEthernetTap.hpp b/osdep/BSDEthernetTap.hpp new file mode 100644 index 0000000..8c6314d --- /dev/null +++ b/osdep/BSDEthernetTap.hpp @@ -0,0 +1,80 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_BSDETHERNETTAP_HPP +#define ZT_BSDETHERNETTAP_HPP + +#include +#include + +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/MulticastGroup.hpp" +#include "../node/MAC.hpp" +#include "Thread.hpp" + +namespace ZeroTier { + +class BSDEthernetTap +{ +public: + BSDEthernetTap( + const char *homePath, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *friendlyName, + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void *arg); + + ~BSDEthernetTap(); + + void setEnabled(bool en); + bool enabled() const; + bool addIp(const InetAddress &ip); + bool removeIp(const InetAddress &ip); + std::vector ips() const; + void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + std::string deviceName() const; + void setFriendlyName(const char *friendlyName); + void scanMulticastGroups(std::vector &added,std::vector &removed); + + void threadMain() + throw(); + +private: + void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); + void *_arg; + uint64_t _nwid; + Thread _thread; + std::string _dev; + std::vector _multicastGroups; + unsigned int _mtu; + unsigned int _metric; + int _fd; + int _shutdownSignalPipe[2]; + volatile bool _enabled; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/Binder.hpp b/osdep/Binder.hpp new file mode 100644 index 0000000..9829f17 --- /dev/null +++ b/osdep/Binder.hpp @@ -0,0 +1,448 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_BINDER_HPP +#define ZT_BINDER_HPP + +#include "../node/Constants.hpp" + +#include +#include +#include +#include + +#ifdef __WINDOWS__ +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#ifdef __LINUX__ +#include +#include +#endif +#endif + +#include +#include +#include +#include +#include + +#include "../node/NonCopyable.hpp" +#include "../node/InetAddress.hpp" +#include "../node/Mutex.hpp" +#include "../node/Utils.hpp" + +#include "Phy.hpp" +#include "OSUtils.hpp" + +/** + * Period between binder rescans/refreshes + * + * OneService also does this on detected restarts. + */ +#define ZT_BINDER_REFRESH_PERIOD 30000 + +namespace ZeroTier { + +/** + * Enumerates local devices and binds to all potential ZeroTier path endpoints + * + * This replaces binding to wildcard (0.0.0.0 and ::0) with explicit binding + * as part of the path to default gateway support. Under the hood it uses + * different queries on different OSes to enumerate devices, and also exposes + * device enumeration and endpoint IP data for use elsewhere. + * + * On OSes that do not support local port enumeration or where this is not + * meaningful, this degrades to binding to wildcard. + */ +class Binder : NonCopyable +{ +private: + struct _Binding + { + _Binding() : + udpSock((PhySocket *)0), + tcpListenSock((PhySocket *)0), + address() {} + + PhySocket *udpSock; + PhySocket *tcpListenSock; + InetAddress address; + }; + +public: + Binder() {} + + /** + * Close all bound ports + * + * This should be called on shutdown. It closes listen sockets and UDP ports + * but not TCP connections from any TCP listen sockets. + * + * @param phy Physical interface + */ + template + void closeAll(Phy &phy) + { + Mutex::Lock _l(_lock); + for(typename std::vector<_Binding>::const_iterator i(_bindings.begin());i!=_bindings.end();++i) { + phy.close(i->udpSock,false); + phy.close(i->tcpListenSock,false); + } + } + + /** + * Scan local devices and addresses and rebind TCP and UDP + * + * This should be called after wake from sleep, on detected network device + * changes, on startup, or periodically (e.g. every 30-60s). + * + * @param phy Physical interface + * @param port Port to bind to on all interfaces (TCP and UDP) + * @param ignoreInterfacesByName Ignore these interfaces by name + * @param ignoreInterfacesByNamePrefix Ignore these interfaces by name-prefix (starts-with, e.g. zt ignores zt*) + * @param ignoreInterfacesByAddress Ignore these interfaces by address + * @tparam PHY_HANDLER_TYPE Type for Phy<> template + * @tparam INTERFACE_CHECKER Type for class containing shouldBindInterface() method + */ + template + void refresh(Phy &phy,unsigned int port,INTERFACE_CHECKER &ifChecker) + { + std::map localIfAddrs; + PhySocket *udps; + //PhySocket *tcps; + Mutex::Lock _l(_lock); + +#ifdef __WINDOWS__ + + char aabuf[32768]; + ULONG aalen = sizeof(aabuf); + if (GetAdaptersAddresses(AF_UNSPEC,GAA_FLAG_SKIP_ANYCAST|GAA_FLAG_SKIP_MULTICAST|GAA_FLAG_SKIP_DNS_SERVER,(void *)0,reinterpret_cast(aabuf),&aalen) == NO_ERROR) { + PIP_ADAPTER_ADDRESSES a = reinterpret_cast(aabuf); + while (a) { + PIP_ADAPTER_UNICAST_ADDRESS ua = a->FirstUnicastAddress; + while (ua) { + InetAddress ip(ua->Address.lpSockaddr); + if (ifChecker.shouldBindInterface("",ip)) { + switch(ip.ipScope()) { + default: break; + case InetAddress::IP_SCOPE_PSEUDOPRIVATE: + case InetAddress::IP_SCOPE_GLOBAL: + case InetAddress::IP_SCOPE_SHARED: + case InetAddress::IP_SCOPE_PRIVATE: + ip.setPort(port); + localIfAddrs.insert(std::pair(ip,std::string())); + break; + } + } + ua = ua->Next; + } + a = a->Next; + } + } + +#else // not __WINDOWS__ + + /* On Linux we use an alternative method if available since getifaddrs() + * gets very slow when there are lots of network namespaces. This won't + * work unless /proc/PID/net/if_inet6 exists and it may not on some + * embedded systems, so revert to getifaddrs() there. */ + +#ifdef __LINUX__ + char fn[256],tmp[256]; + std::set ifnames; + const unsigned long pid = (unsigned long)getpid(); + + // Get all device names + Utils::snprintf(fn,sizeof(fn),"/proc/%lu/net/dev",pid); + FILE *procf = fopen(fn,"r"); + if (procf) { + while (fgets(tmp,sizeof(tmp),procf)) { + tmp[255] = 0; + char *saveptr = (char *)0; + for(char *f=Utils::stok(tmp," \t\r\n:|",&saveptr);(f);f=Utils::stok((char *)0," \t\r\n:|",&saveptr)) { + if ((strcmp(f,"Inter-") != 0)&&(strcmp(f,"face") != 0)&&(f[0] != 0)) + ifnames.insert(f); + break; // we only want the first field + } + } + fclose(procf); + } + + // Get IPv6 addresses (and any device names we don't already know) + Utils::snprintf(fn,sizeof(fn),"/proc/%lu/net/if_inet6",pid); + procf = fopen(fn,"r"); + if (procf) { + while (fgets(tmp,sizeof(tmp),procf)) { + tmp[255] = 0; + char *saveptr = (char *)0; + unsigned char ipbits[16]; + memset(ipbits,0,sizeof(ipbits)); + char *devname = (char *)0; + int n = 0; + for(char *f=Utils::stok(tmp," \t\r\n",&saveptr);(f);f=Utils::stok((char *)0," \t\r\n",&saveptr)) { + switch(n++) { + case 0: // IP in hex + Utils::unhex(f,32,ipbits,16); + break; + case 5: // device name + devname = f; + break; + } + } + if (devname) { + ifnames.insert(devname); + InetAddress ip(ipbits,16,0); + if (ifChecker.shouldBindInterface(devname,ip)) { + switch(ip.ipScope()) { + default: break; + case InetAddress::IP_SCOPE_PSEUDOPRIVATE: + case InetAddress::IP_SCOPE_GLOBAL: + case InetAddress::IP_SCOPE_SHARED: + case InetAddress::IP_SCOPE_PRIVATE: + ip.setPort(port); + localIfAddrs.insert(std::pair(ip,std::string(devname))); + break; + } + } + } + } + fclose(procf); + } + + // Get IPv4 addresses for each device + if (ifnames.size() > 0) { + const int controlfd = (int)socket(AF_INET,SOCK_DGRAM,0); + struct ifconf configuration; + configuration.ifc_len = 0; + configuration.ifc_buf = nullptr; + + if (controlfd < 0) goto ip4_address_error; + + if (ioctl(controlfd, SIOCGIFCONF, &configuration) < 0) goto ip4_address_error; + + configuration.ifc_buf = (char*)malloc(configuration.ifc_len); + + if (ioctl(controlfd, SIOCGIFCONF, &configuration) < 0) goto ip4_address_error; + + for (int i=0; i < (int)(configuration.ifc_len / sizeof(ifreq)); i ++) { + struct ifreq& request = configuration.ifc_req[i]; + struct sockaddr* addr = &request.ifr_ifru.ifru_addr; + if (addr->sa_family != AF_INET) continue; + std::string ifname = request.ifr_ifrn.ifrn_name; + // name can either be just interface name or interface name followed by ':' and arbitrary label + if (ifname.find(':') != std::string::npos) { + ifname = ifname.substr(0, ifname.find(':')); + } + + InetAddress ip(&(((struct sockaddr_in *)addr)->sin_addr),4,0); + if (ifChecker.shouldBindInterface(ifname.c_str(), ip)) { + switch(ip.ipScope()) { + default: break; + case InetAddress::IP_SCOPE_PSEUDOPRIVATE: + case InetAddress::IP_SCOPE_GLOBAL: + case InetAddress::IP_SCOPE_SHARED: + case InetAddress::IP_SCOPE_PRIVATE: + ip.setPort(port); + localIfAddrs.insert(std::pair(ip, ifname)); + break; + } + } + } + + ip4_address_error: + free(configuration.ifc_buf); + if (controlfd > 0) close(controlfd); + } + + const bool gotViaProc = (localIfAddrs.size() > 0); +#else + const bool gotViaProc = false; +#endif + + if (!gotViaProc) { + struct ifaddrs *ifatbl = (struct ifaddrs *)0; + struct ifaddrs *ifa; + if ((getifaddrs(&ifatbl) == 0)&&(ifatbl)) { + ifa = ifatbl; + while (ifa) { + if ((ifa->ifa_name)&&(ifa->ifa_addr)) { + InetAddress ip = *(ifa->ifa_addr); + if (ifChecker.shouldBindInterface(ifa->ifa_name,ip)) { + switch(ip.ipScope()) { + default: break; + case InetAddress::IP_SCOPE_PSEUDOPRIVATE: + case InetAddress::IP_SCOPE_GLOBAL: + case InetAddress::IP_SCOPE_SHARED: + case InetAddress::IP_SCOPE_PRIVATE: + ip.setPort(port); + localIfAddrs.insert(std::pair(ip,std::string(ifa->ifa_name))); + break; + } + } + } + ifa = ifa->ifa_next; + } + freeifaddrs(ifatbl); + } + } + +#endif + + // Default to binding to wildcard if we can't enumerate addresses + if (localIfAddrs.empty()) { + localIfAddrs.insert(std::pair(InetAddress((uint32_t)0,port),std::string())); + localIfAddrs.insert(std::pair(InetAddress((const void *)"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",16,port),std::string())); + } + + // Close any old bindings to anything that doesn't exist anymore + for(typename std::vector<_Binding>::const_iterator bi(_bindings.begin());bi!=_bindings.end();++bi) { + if (localIfAddrs.find(bi->address) == localIfAddrs.end()) { + phy.close(bi->udpSock,false); + phy.close(bi->tcpListenSock,false); + } + } + + std::vector<_Binding> newBindings; + for(std::map::const_iterator ii(localIfAddrs.begin());ii!=localIfAddrs.end();++ii) { + typename std::vector<_Binding>::const_iterator bi(_bindings.begin()); + while (bi != _bindings.end()) { + if (bi->address == ii->first) { + newBindings.push_back(*bi); + break; + } + ++bi; + } + + if (bi == _bindings.end()) { + udps = phy.udpBind(reinterpret_cast(&(ii->first)),(void *)0,ZT_UDP_DESIRED_BUF_SIZE); + if (udps) { + //tcps = phy.tcpListen(reinterpret_cast(&ii),(void *)0); + //if (tcps) { +#ifdef __LINUX__ + // Bind Linux sockets to their device so routes tha we manage do not override physical routes (wish all platforms had this!) + if (ii->second.length() > 0) { + int fd = (int)Phy::getDescriptor(udps); + char tmp[256]; + Utils::scopy(tmp,sizeof(tmp),ii->second.c_str()); + if (fd >= 0) { + if (setsockopt(fd,SOL_SOCKET,SO_BINDTODEVICE,tmp,strlen(tmp)) != 0) { + fprintf(stderr,"WARNING: unable to set SO_BINDTODEVICE to bind %s to %s\n",ii->first.toIpString().c_str(),ii->second.c_str()); + } + } + } +#endif // __LINUX__ + newBindings.push_back(_Binding()); + newBindings.back().udpSock = udps; + //newBindings.back().tcpListenSock = tcps; + newBindings.back().address = ii->first; + //} else { + // phy.close(udps,false); + //} + } + } + } + + // Swapping pointers and then letting the old one fall out of scope is faster than copying again + _bindings.swap(newBindings); + } + + /** + * Send a UDP packet from the specified local interface, or all + * + * Unfortunately even by examining the routing table there is no ultimately + * robust way to tell where we might reach another host that works in all + * environments. As a result, we send packets with null (wildcard) local + * addresses from *every* bound interface. + * + * These are typically initial HELLOs, path probes, etc., since normal + * conversations will have a local endpoint address. So the cost is low and + * if the peer is not reachable via that route then the packet will go + * nowhere and nothing will happen. + * + * It will of course only send via interface bindings of the same socket + * family. No point in sending V4 via V6 or vice versa. + * + * In any case on most hosts there's only one or two interfaces that we + * will use, so none of this is particularly costly. + * + * @param local Local interface address or null address for 'all' + * @param remote Remote address + * @param data Data to send + * @param len Length of data + * @param v4ttl If non-zero, send this packet with the specified IP TTL (IPv4 only) + */ + template + inline bool udpSend(Phy &phy,const InetAddress &local,const InetAddress &remote,const void *data,unsigned int len,unsigned int v4ttl = 0) const + { + Mutex::Lock _l(_lock); + if (local) { + for(typename std::vector<_Binding>::const_iterator i(_bindings.begin());i!=_bindings.end();++i) { + if (i->address == local) { + if ((v4ttl)&&(local.ss_family == AF_INET)) + phy.setIp4UdpTtl(i->udpSock,v4ttl); + const bool result = phy.udpSend(i->udpSock,reinterpret_cast(&remote),data,len); + if ((v4ttl)&&(local.ss_family == AF_INET)) + phy.setIp4UdpTtl(i->udpSock,255); + return result; + } + } + return false; + } else { + bool result = false; + for(typename std::vector<_Binding>::const_iterator i(_bindings.begin());i!=_bindings.end();++i) { + if (i->address.ss_family == remote.ss_family) { + if ((v4ttl)&&(remote.ss_family == AF_INET)) + phy.setIp4UdpTtl(i->udpSock,v4ttl); + result |= phy.udpSend(i->udpSock,reinterpret_cast(&remote),data,len); + if ((v4ttl)&&(remote.ss_family == AF_INET)) + phy.setIp4UdpTtl(i->udpSock,255); + } + } + return result; + } + } + + /** + * @return All currently bound local interface addresses + */ + inline std::vector allBoundLocalInterfaceAddresses() + { + Mutex::Lock _l(_lock); + std::vector aa; + for(std::vector<_Binding>::const_iterator i(_bindings.begin());i!=_bindings.end();++i) + aa.push_back(i->address); + return aa; + } + +private: + std::vector<_Binding> _bindings; + Mutex _lock; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/BlockingQueue.hpp b/osdep/BlockingQueue.hpp new file mode 100644 index 0000000..6172f4d --- /dev/null +++ b/osdep/BlockingQueue.hpp @@ -0,0 +1,64 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_BLOCKINGQUEUE_HPP +#define ZT_BLOCKINGQUEUE_HPP + +#include +#include +#include + +namespace ZeroTier { + +/** + * Simple C++11 thread-safe queue + * + * Do not use in node/ since we have not gone C++11 there yet. + */ +template +class BlockingQueue +{ +public: + BlockingQueue(void) {} + + inline void post(T t) + { + std::lock_guard lock(m); + q.push(t); + c.notify_one(); + } + + inline T get(void) + { + std::unique_lock lock(m); + while(q.empty()) + c.wait(lock); + T val = q.front(); + q.pop(); + return val; + } + +private: + std::queue q; + mutable std::mutex m; + std::condition_variable c; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/Http.cpp b/osdep/Http.cpp new file mode 100644 index 0000000..064ccd0 --- /dev/null +++ b/osdep/Http.cpp @@ -0,0 +1,291 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include + +#include "Http.hpp" +#include "Phy.hpp" +#include "OSUtils.hpp" +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" + +#ifdef ZT_USE_SYSTEM_HTTP_PARSER +#include +#else +#include "../ext/http-parser/http_parser.h" +#endif + +namespace ZeroTier { + +namespace { + +static int ShttpOnMessageBegin(http_parser *parser); +static int ShttpOnUrl(http_parser *parser,const char *ptr,size_t length); +#if (HTTP_PARSER_VERSION_MAJOR >= 2) && (HTTP_PARSER_VERSION_MINOR >= 2) +static int ShttpOnStatus(http_parser *parser,const char *ptr,size_t length); +#else +static int ShttpOnStatus(http_parser *parser); +#endif +static int ShttpOnHeaderField(http_parser *parser,const char *ptr,size_t length); +static int ShttpOnValue(http_parser *parser,const char *ptr,size_t length); +static int ShttpOnHeadersComplete(http_parser *parser); +static int ShttpOnBody(http_parser *parser,const char *ptr,size_t length); +static int ShttpOnMessageComplete(http_parser *parser); + +#if (HTTP_PARSER_VERSION_MAJOR >= 2) && (HTTP_PARSER_VERSION_MINOR >= 1) +static const struct http_parser_settings HTTP_PARSER_SETTINGS = { + ShttpOnMessageBegin, + ShttpOnUrl, + ShttpOnStatus, + ShttpOnHeaderField, + ShttpOnValue, + ShttpOnHeadersComplete, + ShttpOnBody, + ShttpOnMessageComplete +}; +#else +static const struct http_parser_settings HTTP_PARSER_SETTINGS = { + ShttpOnMessageBegin, + ShttpOnUrl, + ShttpOnHeaderField, + ShttpOnValue, + ShttpOnHeadersComplete, + ShttpOnBody, + ShttpOnMessageComplete +}; +#endif + +struct HttpPhyHandler +{ + // not used + inline void phyOnDatagram(PhySocket *sock,void **uptr,const struct sockaddr *localAddr,const struct sockaddr *from,void *data,unsigned long len) {} + inline void phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from) {} + + inline void phyOnTcpConnect(PhySocket *sock,void **uptr,bool success) + { + if (success) { + phy->setNotifyWritable(sock,true); + } else { + *responseBody = "connection failed"; + error = true; + done = true; + } + } + + inline void phyOnTcpClose(PhySocket *sock,void **uptr) + { + done = true; + } + + inline void phyOnTcpData(PhySocket *sock,void **uptr,void *data,unsigned long len) + { + lastActivity = OSUtils::now(); + http_parser_execute(&parser,&HTTP_PARSER_SETTINGS,(const char *)data,len); + if ((parser.upgrade)||(parser.http_errno != HPE_OK)) + phy->close(sock); + } + + inline void phyOnTcpWritable(PhySocket *sock,void **uptr) + { + if (writePtr < writeSize) { + long n = phy->streamSend(sock,writeBuf + writePtr,writeSize - writePtr,true); + if (n > 0) + writePtr += n; + } + if (writePtr >= writeSize) + phy->setNotifyWritable(sock,false); + } + + inline void phyOnFileDescriptorActivity(PhySocket *sock,void **uptr,bool readable,bool writable) {} +#ifdef __UNIX_LIKE__ + inline void phyOnUnixAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN) {} + inline void phyOnUnixClose(PhySocket *sock,void **uptr) {} + inline void phyOnUnixData(PhySocket *sock,void **uptr,void *data,unsigned long len) {} + inline void phyOnUnixWritable(PhySocket *sock,void **uptr) {} +#endif // __UNIX_LIKE__ + + http_parser parser; + std::string currentHeaderField; + std::string currentHeaderValue; + unsigned long messageSize; + unsigned long writePtr; + uint64_t lastActivity; + unsigned long writeSize; + char writeBuf[32768]; + + unsigned long maxResponseSize; + std::map *responseHeaders; + std::string *responseBody; + bool error; + bool done; + + Phy *phy; + PhySocket *sock; +}; + +static int ShttpOnMessageBegin(http_parser *parser) +{ + return 0; +} +static int ShttpOnUrl(http_parser *parser,const char *ptr,size_t length) +{ + return 0; +} +#if (HTTP_PARSER_VERSION_MAJOR >= 2) && (HTTP_PARSER_VERSION_MINOR >= 2) +static int ShttpOnStatus(http_parser *parser,const char *ptr,size_t length) +#else +static int ShttpOnStatus(http_parser *parser) +#endif +{ + /* + HttpPhyHandler *hh = reinterpret_cast(parser->data); + hh->messageSize += (unsigned long)length; + if (hh->messageSize > hh->maxResponseSize) + return -1; + */ + return 0; +} +static int ShttpOnHeaderField(http_parser *parser,const char *ptr,size_t length) +{ + HttpPhyHandler *hh = reinterpret_cast(parser->data); + hh->messageSize += (unsigned long)length; + if (hh->messageSize > hh->maxResponseSize) + return -1; + if ((hh->currentHeaderField.length())&&(hh->currentHeaderValue.length())) { + (*hh->responseHeaders)[hh->currentHeaderField] = hh->currentHeaderValue; + hh->currentHeaderField = ""; + hh->currentHeaderValue = ""; + } + for(size_t i=0;icurrentHeaderField.push_back(OSUtils::toLower(ptr[i])); + return 0; +} +static int ShttpOnValue(http_parser *parser,const char *ptr,size_t length) +{ + HttpPhyHandler *hh = reinterpret_cast(parser->data); + hh->messageSize += (unsigned long)length; + if (hh->messageSize > hh->maxResponseSize) + return -1; + hh->currentHeaderValue.append(ptr,length); + return 0; +} +static int ShttpOnHeadersComplete(http_parser *parser) +{ + HttpPhyHandler *hh = reinterpret_cast(parser->data); + if ((hh->currentHeaderField.length())&&(hh->currentHeaderValue.length())) + (*hh->responseHeaders)[hh->currentHeaderField] = hh->currentHeaderValue; + return 0; +} +static int ShttpOnBody(http_parser *parser,const char *ptr,size_t length) +{ + HttpPhyHandler *hh = reinterpret_cast(parser->data); + hh->messageSize += (unsigned long)length; + if (hh->messageSize > hh->maxResponseSize) + return -1; + hh->responseBody->append(ptr,length); + return 0; +} +static int ShttpOnMessageComplete(http_parser *parser) +{ + HttpPhyHandler *hh = reinterpret_cast(parser->data); + hh->phy->close(hh->sock); + return 0; +} + +} // anonymous namespace + +unsigned int Http::_do( + const char *method, + unsigned long maxResponseSize, + unsigned long timeout, + const struct sockaddr *remoteAddress, + const char *path, + const std::map &requestHeaders, + const void *requestBody, + unsigned long requestBodyLength, + std::map &responseHeaders, + std::string &responseBody) +{ + try { + responseHeaders.clear(); + responseBody = ""; + + HttpPhyHandler handler; + + http_parser_init(&(handler.parser),HTTP_RESPONSE); + handler.parser.data = (void *)&handler; + handler.messageSize = 0; + handler.writePtr = 0; + handler.lastActivity = OSUtils::now(); + + try { + handler.writeSize = Utils::snprintf(handler.writeBuf,sizeof(handler.writeBuf),"%s %s HTTP/1.1\r\n",method,path); + for(std::map::const_iterator h(requestHeaders.begin());h!=requestHeaders.end();++h) + handler.writeSize += Utils::snprintf(handler.writeBuf + handler.writeSize,sizeof(handler.writeBuf) - handler.writeSize,"%s: %s\r\n",h->first.c_str(),h->second.c_str()); + handler.writeSize += Utils::snprintf(handler.writeBuf + handler.writeSize,sizeof(handler.writeBuf) - handler.writeSize,"\r\n"); + if ((requestBody)&&(requestBodyLength)) { + if ((handler.writeSize + requestBodyLength) > sizeof(handler.writeBuf)) { + responseBody = "request too large"; + return 0; + } + memcpy(handler.writeBuf + handler.writeSize,requestBody,requestBodyLength); + handler.writeSize += requestBodyLength; + } + } catch ( ... ) { + responseBody = "request too large"; + return 0; + } + + handler.maxResponseSize = maxResponseSize; + handler.responseHeaders = &responseHeaders; + handler.responseBody = &responseBody; + handler.error = false; + handler.done = false; + + Phy phy(&handler,true,true); + + bool instantConnect = false; + handler.phy = &phy; + handler.sock = phy.tcpConnect((const struct sockaddr *)remoteAddress,instantConnect,(void *)0,true); + if (!handler.sock) { + responseBody = "connection failed (2)"; + return 0; + } + + while (!handler.done) { + phy.poll(timeout / 2); + if ((timeout)&&((unsigned long)(OSUtils::now() - handler.lastActivity) > timeout)) { + phy.close(handler.sock); + responseBody = "timed out"; + return 0; + } + } + + return ((handler.error) ? 0 : ((handler.parser.http_errno != HPE_OK) ? 0 : handler.parser.status_code)); + } catch (std::exception &exc) { + responseBody = exc.what(); + return 0; + } catch ( ... ) { + responseBody = "unknown exception"; + return 0; + } +} + +} // namespace ZeroTier diff --git a/osdep/Http.hpp b/osdep/Http.hpp new file mode 100644 index 0000000..e7d4d03 --- /dev/null +++ b/osdep/Http.hpp @@ -0,0 +1,187 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_HTTP_HPP +#define ZT_HTTP_HPP + +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +namespace ZeroTier { + +/** + * Simple synchronous HTTP client used for updater and cli + */ +class Http +{ +public: + /** + * Make HTTP GET request + * + * The caller must set all headers, including Host. + * + * @return HTTP status code or 0 on error (responseBody will contain error message) + */ + static inline unsigned int GET( + unsigned long maxResponseSize, + unsigned long timeout, + const struct sockaddr *remoteAddress, + const char *path, + const std::map &requestHeaders, + std::map &responseHeaders, + std::string &responseBody) + { + return _do( + "GET", + maxResponseSize, + timeout, + remoteAddress, + path, + requestHeaders, + (const void *)0, + 0, + responseHeaders, + responseBody); + } + + /** + * Make HTTP DELETE request + * + * The caller must set all headers, including Host. + * + * @return HTTP status code or 0 on error (responseBody will contain error message) + */ + static inline unsigned int DEL( + unsigned long maxResponseSize, + unsigned long timeout, + const struct sockaddr *remoteAddress, + const char *path, + const std::map &requestHeaders, + std::map &responseHeaders, + std::string &responseBody) + { + return _do( + "DELETE", + maxResponseSize, + timeout, + remoteAddress, + path, + requestHeaders, + (const void *)0, + 0, + responseHeaders, + responseBody); + } + + /** + * Make HTTP POST request + * + * It is the responsibility of the caller to set all headers. With POST, the + * Content-Length and Content-Type headers must be set or the POST will not + * work. + * + * @return HTTP status code or 0 on error (responseBody will contain error message) + */ + static inline unsigned int POST( + unsigned long maxResponseSize, + unsigned long timeout, + const struct sockaddr *remoteAddress, + const char *path, + const std::map &requestHeaders, + const void *postData, + unsigned long postDataLength, + std::map &responseHeaders, + std::string &responseBody) + { + return _do( + "POST", + maxResponseSize, + timeout, + remoteAddress, + path, + requestHeaders, + postData, + postDataLength, + responseHeaders, + responseBody); + } + + /** + * Make HTTP PUT request + * + * It is the responsibility of the caller to set all headers. With PUT, the + * Content-Length and Content-Type headers must be set or the PUT will not + * work. + * + * @return HTTP status code or 0 on error (responseBody will contain error message) + */ + static inline unsigned int PUT( + unsigned long maxResponseSize, + unsigned long timeout, + const struct sockaddr *remoteAddress, + const char *path, + const std::map &requestHeaders, + const void *postData, + unsigned long postDataLength, + std::map &responseHeaders, + std::string &responseBody) + { + return _do( + "PUT", + maxResponseSize, + timeout, + remoteAddress, + path, + requestHeaders, + postData, + postDataLength, + responseHeaders, + responseBody); + } + +private: + static unsigned int _do( + const char *method, + unsigned long maxResponseSize, + unsigned long timeout, + const struct sockaddr *remoteAddress, + const char *path, + const std::map &requestHeaders, + const void *requestBody, + unsigned long requestBodyLength, + std::map &responseHeaders, + std::string &responseBody); +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/LinuxEthernetTap.cpp b/osdep/LinuxEthernetTap.cpp new file mode 100644 index 0000000..f74efc0 --- /dev/null +++ b/osdep/LinuxEthernetTap.cpp @@ -0,0 +1,502 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" +#include "../node/Mutex.hpp" +#include "../node/Dictionary.hpp" +#include "OSUtils.hpp" +#include "LinuxEthernetTap.hpp" + +// ff:ff:ff:ff:ff:ff with no ADI +static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); + +namespace ZeroTier { + +static Mutex __tapCreateLock; + +LinuxEthernetTap::LinuxEthernetTap( + const char *homePath, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *friendlyName, + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void *arg) : + _handler(handler), + _arg(arg), + _nwid(nwid), + _homePath(homePath), + _mtu(mtu), + _fd(0), + _enabled(true) +{ + char procpath[128],nwids[32]; + struct stat sbuf; + + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid); + + Mutex::Lock _l(__tapCreateLock); // create only one tap at a time, globally + + if (mtu > 2800) + throw std::runtime_error("max tap MTU is 2800"); + + _fd = ::open("/dev/net/tun",O_RDWR); + if (_fd <= 0) { + _fd = ::open("/dev/tun",O_RDWR); + if (_fd <= 0) + throw std::runtime_error(std::string("could not open TUN/TAP device: ") + strerror(errno)); + } + + struct ifreq ifr; + memset(&ifr,0,sizeof(ifr)); + + // Try to recall our last device name, or pick an unused one if that fails. + std::map globalDeviceMap; + FILE *devmapf = fopen((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),"r"); + if (devmapf) { + char buf[256]; + while (fgets(buf,sizeof(buf),devmapf)) { + char *x = (char *)0; + char *y = (char *)0; + char *saveptr = (char *)0; + for(char *f=Utils::stok(buf,"\r\n=",&saveptr);(f);f=Utils::stok((char *)0,"\r\n=",&saveptr)) { + if (!x) x = f; + else if (!y) y = f; + else break; + } + if ((x)&&(y)&&(x[0])&&(y[0])) + globalDeviceMap[x] = y; + } + fclose(devmapf); + } + bool recalledDevice = false; + std::map::const_iterator gdmEntry = globalDeviceMap.find(nwids); + if (gdmEntry != globalDeviceMap.end()) { + Utils::scopy(ifr.ifr_name,sizeof(ifr.ifr_name),gdmEntry->second.c_str()); + Utils::snprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name); + recalledDevice = (stat(procpath,&sbuf) != 0); + } + if (!recalledDevice) { + int devno = 0; + do { +#ifdef __SYNOLOGY__ + devno+=50; // Arbitrary number to prevent interface name conflicts + Utils::snprintf(ifr.ifr_name,sizeof(ifr.ifr_name),"eth%d",devno++); +#else + Utils::snprintf(ifr.ifr_name,sizeof(ifr.ifr_name),"zt%d",devno++); +#endif + Utils::snprintf(procpath,sizeof(procpath),"/proc/sys/net/ipv4/conf/%s",ifr.ifr_name); + } while (stat(procpath,&sbuf) == 0); // try zt#++ until we find one that does not exist + } + + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + if (ioctl(_fd,TUNSETIFF,(void *)&ifr) < 0) { + ::close(_fd); + throw std::runtime_error("unable to configure TUN/TAP device for TAP operation"); + } + + _dev = ifr.ifr_name; + + ::ioctl(_fd,TUNSETPERSIST,0); // valgrind may generate a false alarm here + + // Open an arbitrary socket to talk to netlink + int sock = socket(AF_INET,SOCK_DGRAM,0); + if (sock <= 0) { + ::close(_fd); + throw std::runtime_error("unable to open netlink socket"); + } + + // Set MAC address + ifr.ifr_ifru.ifru_hwaddr.sa_family = ARPHRD_ETHER; + mac.copyTo(ifr.ifr_ifru.ifru_hwaddr.sa_data,6); + if (ioctl(sock,SIOCSIFHWADDR,(void *)&ifr) < 0) { + ::close(_fd); + ::close(sock); + throw std::runtime_error("unable to configure TAP hardware (MAC) address"); + return; + } + + // Set MTU + ifr.ifr_ifru.ifru_mtu = (int)mtu; + if (ioctl(sock,SIOCSIFMTU,(void *)&ifr) < 0) { + ::close(_fd); + ::close(sock); + throw std::runtime_error("unable to configure TAP MTU"); + } + + if (fcntl(_fd,F_SETFL,fcntl(_fd,F_GETFL) & ~O_NONBLOCK) == -1) { + ::close(_fd); + throw std::runtime_error("unable to set flags on file descriptor for TAP device"); + } + + /* Bring interface up */ + if (ioctl(sock,SIOCGIFFLAGS,(void *)&ifr) < 0) { + ::close(_fd); + ::close(sock); + throw std::runtime_error("unable to get TAP interface flags"); + } + ifr.ifr_flags |= IFF_UP; + if (ioctl(sock,SIOCSIFFLAGS,(void *)&ifr) < 0) { + ::close(_fd); + ::close(sock); + throw std::runtime_error("unable to set TAP interface flags"); + } + + ::close(sock); + + // Set close-on-exec so that devices cannot persist if we fork/exec for update + ::fcntl(_fd,F_SETFD,fcntl(_fd,F_GETFD) | FD_CLOEXEC); + + (void)::pipe(_shutdownSignalPipe); + + globalDeviceMap[nwids] = _dev; + devmapf = fopen((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),"w"); + if (devmapf) { + gdmEntry = globalDeviceMap.begin(); + while (gdmEntry != globalDeviceMap.end()) { + fprintf(devmapf,"%s=%s\n",gdmEntry->first.c_str(),gdmEntry->second.c_str()); + ++gdmEntry; + } + fclose(devmapf); + } + + _thread = Thread::start(this); +} + +LinuxEthernetTap::~LinuxEthernetTap() +{ + (void)::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit + Thread::join(_thread); + ::close(_fd); + ::close(_shutdownSignalPipe[0]); + ::close(_shutdownSignalPipe[1]); +} + +void LinuxEthernetTap::setEnabled(bool en) +{ + _enabled = en; +} + +bool LinuxEthernetTap::enabled() const +{ + return _enabled; +} + +static bool ___removeIp(const std::string &_dev,const InetAddress &ip) +{ + long cpid = (long)vfork(); + if (cpid == 0) { + OSUtils::redirectUnixOutputs("/dev/null",(const char *)0); + setenv("PATH", "/sbin:/bin:/usr/sbin:/usr/bin", 1); + ::execlp("ip","ip","addr","del",ip.toString().c_str(),"dev",_dev.c_str(),(const char *)0); + ::_exit(-1); + } else { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } +} + +#ifdef __SYNOLOGY__ +bool LinuxEthernetTap::addIpSyn(std::vector ips) +{ + // Here we fill out interface config (ifcfg-dev) to prevent it from being killed + std::string filepath = "/etc/sysconfig/network-scripts/ifcfg-"+_dev; + std::string cfg_contents = "DEVICE="+_dev+"\nBOOTPROTO=static"; + int ip4=0,ip6=0,ip4_tot=0,ip6_tot=0; + + long cpid = (long)vfork(); + if (cpid == 0) { + OSUtils::redirectUnixOutputs("/dev/null",(const char *)0); + setenv("PATH", "/sbin:/bin:/usr/sbin:/usr/bin", 1); + // We must know if there is at least (one) of each protocol version so we + // can properly enumerate address/netmask combinations in the ifcfg-dev file + for(int i=0; i<(int)ips.size(); i++) { + if (ips[i].isV4()) + ip4_tot++; + else + ip6_tot++; + } + // Assemble and write contents of ifcfg-dev file + for(int i=0; i<(int)ips.size(); i++) { + if (ips[i].isV4()) { + std::string numstr4 = ip4_tot > 1 ? std::to_string(ip4) : ""; + cfg_contents += "\nIPADDR"+numstr4+"="+ips[i].toIpString() + + "\nNETMASK"+numstr4+"="+ips[i].netmask().toIpString()+"\n"; + ip4++; + } + else { + std::string numstr6 = ip6_tot > 1 ? std::to_string(ip6) : ""; + cfg_contents += "\nIPV6ADDR"+numstr6+"="+ips[i].toIpString() + + "\nNETMASK"+numstr6+"="+ips[i].netmask().toIpString()+"\n"; + ip6++; + } + } + OSUtils::writeFile(filepath.c_str(), cfg_contents.c_str(), cfg_contents.length()); + // Finaly, add IPs + for(int i=0; i<(int)ips.size(); i++){ + if (ips[i].isV4()) + ::execlp("ip","ip","addr","add",ips[i].toString().c_str(),"broadcast",ips[i].broadcast().toIpString().c_str(),"dev",_dev.c_str(),(const char *)0); + else + ::execlp("ip","ip","addr","add",ips[i].toString().c_str(),"dev",_dev.c_str(),(const char *)0); + } + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + return true; +} +#endif // __SYNOLOGY__ + +bool LinuxEthernetTap::addIp(const InetAddress &ip) +{ + if (!ip) + return false; + + std::vector allIps(ips()); + if (std::binary_search(allIps.begin(),allIps.end(),ip)) + return true; + + // Remove and reconfigure if address is the same but netmask is different + for(std::vector::iterator i(allIps.begin());i!=allIps.end();++i) { + if (i->ipsEqual(ip)) + ___removeIp(_dev,*i); + } + + long cpid = (long)vfork(); + if (cpid == 0) { + OSUtils::redirectUnixOutputs("/dev/null",(const char *)0); + setenv("PATH", "/sbin:/bin:/usr/sbin:/usr/bin", 1); + if (ip.isV4()) { + ::execlp("ip","ip","addr","add",ip.toString().c_str(),"broadcast",ip.broadcast().toIpString().c_str(),"dev",_dev.c_str(),(const char *)0); + } else { + ::execlp("ip","ip","addr","add",ip.toString().c_str(),"dev",_dev.c_str(),(const char *)0); + } + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + + return false; +} + +bool LinuxEthernetTap::removeIp(const InetAddress &ip) +{ + if (!ip) + return true; + std::vector allIps(ips()); + if (std::find(allIps.begin(),allIps.end(),ip) != allIps.end()) { + if (___removeIp(_dev,ip)) + return true; + } + return false; +} + +std::vector LinuxEthernetTap::ips() const +{ + struct ifaddrs *ifa = (struct ifaddrs *)0; + if (getifaddrs(&ifa)) + return std::vector(); + + std::vector r; + + struct ifaddrs *p = ifa; + while (p) { + if ((!strcmp(p->ifa_name,_dev.c_str()))&&(p->ifa_addr)&&(p->ifa_netmask)&&(p->ifa_addr->sa_family == p->ifa_netmask->sa_family)) { + switch(p->ifa_addr->sa_family) { + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *)p->ifa_addr; + struct sockaddr_in *nm = (struct sockaddr_in *)p->ifa_netmask; + r.push_back(InetAddress(&(sin->sin_addr.s_addr),4,Utils::countBits((uint32_t)nm->sin_addr.s_addr))); + } break; + case AF_INET6: { + struct sockaddr_in6 *sin = (struct sockaddr_in6 *)p->ifa_addr; + struct sockaddr_in6 *nm = (struct sockaddr_in6 *)p->ifa_netmask; + uint32_t b[4]; + memcpy(b,nm->sin6_addr.s6_addr,sizeof(b)); + r.push_back(InetAddress(sin->sin6_addr.s6_addr,16,Utils::countBits(b[0]) + Utils::countBits(b[1]) + Utils::countBits(b[2]) + Utils::countBits(b[3]))); + } break; + } + } + p = p->ifa_next; + } + + if (ifa) + freeifaddrs(ifa); + + std::sort(r.begin(),r.end()); + r.erase(std::unique(r.begin(),r.end()),r.end()); + + return r; +} + +void LinuxEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + char putBuf[8194]; + if ((_fd > 0)&&(len <= _mtu)&&(_enabled)) { + to.copyTo(putBuf,6); + from.copyTo(putBuf + 6,6); + *((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType); + memcpy(putBuf + 14,data,len); + len += 14; + (void)::write(_fd,putBuf,len); + } +} + +std::string LinuxEthernetTap::deviceName() const +{ + return _dev; +} + +void LinuxEthernetTap::setFriendlyName(const char *friendlyName) +{ +} + +void LinuxEthernetTap::scanMulticastGroups(std::vector &added,std::vector &removed) +{ + char *ptr,*ptr2; + unsigned char mac[6]; + std::vector newGroups; + + int fd = ::open("/proc/net/dev_mcast",O_RDONLY); + if (fd > 0) { + char buf[131072]; + int n = (int)::read(fd,buf,sizeof(buf)); + if ((n > 0)&&(n < (int)sizeof(buf))) { + buf[n] = (char)0; + for(char *l=strtok_r(buf,"\r\n",&ptr);(l);l=strtok_r((char *)0,"\r\n",&ptr)) { + int fno = 0; + char *devname = (char *)0; + char *mcastmac = (char *)0; + for(char *f=strtok_r(l," \t",&ptr2);(f);f=strtok_r((char *)0," \t",&ptr2)) { + if (fno == 1) + devname = f; + else if (fno == 4) + mcastmac = f; + ++fno; + } + if ((devname)&&(!strcmp(devname,_dev.c_str()))&&(mcastmac)&&(Utils::unhex(mcastmac,mac,6) == 6)) + newGroups.push_back(MulticastGroup(MAC(mac,6),0)); + } + } + ::close(fd); + } + + std::vector allIps(ips()); + for(std::vector::iterator ip(allIps.begin());ip!=allIps.end();++ip) + newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip)); + + std::sort(newGroups.begin(),newGroups.end()); + newGroups.erase(std::unique(newGroups.begin(),newGroups.end()),newGroups.end()); + + for(std::vector::iterator m(newGroups.begin());m!=newGroups.end();++m) { + if (!std::binary_search(_multicastGroups.begin(),_multicastGroups.end(),*m)) + added.push_back(*m); + } + for(std::vector::iterator m(_multicastGroups.begin());m!=_multicastGroups.end();++m) { + if (!std::binary_search(newGroups.begin(),newGroups.end(),*m)) + removed.push_back(*m); + } + + _multicastGroups.swap(newGroups); +} + +void LinuxEthernetTap::threadMain() + throw() +{ + fd_set readfds,nullfds; + MAC to,from; + int n,nfds,r; + char getBuf[8194]; + + Thread::sleep(500); + + FD_ZERO(&readfds); + FD_ZERO(&nullfds); + nfds = (int)std::max(_shutdownSignalPipe[0],_fd) + 1; + + r = 0; + for(;;) { + FD_SET(_shutdownSignalPipe[0],&readfds); + FD_SET(_fd,&readfds); + select(nfds,&readfds,&nullfds,&nullfds,(struct timeval *)0); + + if (FD_ISSET(_shutdownSignalPipe[0],&readfds)) // writes to shutdown pipe terminate thread + break; + + if (FD_ISSET(_fd,&readfds)) { + n = (int)::read(_fd,getBuf + r,sizeof(getBuf) - r); + if (n < 0) { + if ((errno != EINTR)&&(errno != ETIMEDOUT)) + break; + } else { + // Some tap drivers like to send the ethernet frame and the + // payload in two chunks, so handle that by accumulating + // data until we have at least a frame. + r += n; + if (r > 14) { + if (r > ((int)_mtu + 14)) // sanity check for weird TAP behavior on some platforms + r = _mtu + 14; + + if (_enabled) { + to.setTo(getBuf,6); + from.setTo(getBuf + 6,6); + unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); + // TODO: VLAN support + _handler(_arg,(void *)0,_nwid,from,to,etherType,0,(const void *)(getBuf + 14),r - 14); + } + + r = 0; + } + } + } + } +} + +} // namespace ZeroTier diff --git a/osdep/LinuxEthernetTap.hpp b/osdep/LinuxEthernetTap.hpp new file mode 100644 index 0000000..a2a00a7 --- /dev/null +++ b/osdep/LinuxEthernetTap.hpp @@ -0,0 +1,84 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_LINUXETHERNETTAP_HPP +#define ZT_LINUXETHERNETTAP_HPP + +#include +#include + +#include +#include +#include + +#include "../node/MulticastGroup.hpp" +#include "Thread.hpp" + +namespace ZeroTier { + +/** + * Linux Ethernet tap using kernel tun/tap driver + */ +class LinuxEthernetTap +{ +public: + LinuxEthernetTap( + const char *homePath, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *friendlyName, + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void *arg); + + ~LinuxEthernetTap(); + + void setEnabled(bool en); + bool enabled() const; + bool addIp(const InetAddress &ip); +#ifdef __SYNOLOGY__ + bool addIpSyn(std::vector ips); +#endif + bool removeIp(const InetAddress &ip); + std::vector ips() const; + void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + std::string deviceName() const; + void setFriendlyName(const char *friendlyName); + void scanMulticastGroups(std::vector &added,std::vector &removed); + + void threadMain() + throw(); + +private: + void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); + void *_arg; + uint64_t _nwid; + Thread _thread; + std::string _homePath; + std::string _dev; + std::vector _multicastGroups; + unsigned int _mtu; + int _fd; + int _shutdownSignalPipe[2]; + volatile bool _enabled; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/ManagedRoute.cpp b/osdep/ManagedRoute.cpp new file mode 100644 index 0000000..3a020d6 --- /dev/null +++ b/osdep/ManagedRoute.cpp @@ -0,0 +1,554 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include "../node/Constants.hpp" + +#include +#include +#include +#include + +#ifdef __WINDOWS__ +#include +#include +#include +#include +#endif + +#ifdef __UNIX_LIKE__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __BSD__ +#include +#include +#endif +#include +#endif + +#include +#include +#include + +#include "ManagedRoute.hpp" + +#define ZT_BSD_ROUTE_CMD "/sbin/route" +#define ZT_LINUX_IP_COMMAND "/sbin/ip" +#define ZT_LINUX_IP_COMMAND_2 "/usr/sbin/ip" + +namespace ZeroTier { + +namespace { + +// Fork a target into two more specific targets e.g. 0.0.0.0/0 -> 0.0.0.0/1, 128.0.0.0/1 +// If the target is already maximally-specific, 'right' will be unchanged and 'left' will be 't' +static void _forkTarget(const InetAddress &t,InetAddress &left,InetAddress &right) +{ + const unsigned int bits = t.netmaskBits() + 1; + left = t; + if (t.ss_family == AF_INET) { + if (bits <= 32) { + left.setPort(bits); + right = t; + reinterpret_cast(&right)->sin_addr.s_addr ^= Utils::hton((uint32_t)(1 << (32 - bits))); + right.setPort(bits); + } else { + right.zero(); + } + } else if (t.ss_family == AF_INET6) { + if (bits <= 128) { + left.setPort(bits); + right = t; + uint8_t *b = reinterpret_cast(reinterpret_cast(&right)->sin6_addr.s6_addr); + b[bits / 8] ^= 1 << (8 - (bits % 8)); + right.setPort(bits); + } else { + right.zero(); + } + } +} + +struct _RTE +{ + InetAddress target; + InetAddress via; + char device[128]; + int metric; + bool ifscope; +}; + +#ifdef __BSD__ // ------------------------------------------------------------ +#define ZT_ROUTING_SUPPORT_FOUND 1 + +static std::vector<_RTE> _getRTEs(const InetAddress &target,bool contains) +{ + std::vector<_RTE> rtes; + int mib[6]; + size_t needed; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = 0; + mib[4] = NET_RT_DUMP; + mib[5] = 0; + if (!sysctl(mib,6,NULL,&needed,NULL,0)) { + if (needed <= 0) + return rtes; + + char *buf = (char *)::malloc(needed); + if (buf) { + if (!sysctl(mib,6,buf,&needed,NULL,0)) { + struct rt_msghdr *rtm; + for(char *next=buf,*end=buf+needed;nextrtm_msglen; + + InetAddress sa_t,sa_v; + int deviceIndex = -9999; + + if (((rtm->rtm_flags & RTF_LLINFO) == 0)&&((rtm->rtm_flags & RTF_HOST) == 0)&&((rtm->rtm_flags & RTF_UP) != 0)&&((rtm->rtm_flags & RTF_MULTICAST) == 0)) { + int which = 0; + while (saptr < saend) { + struct sockaddr *sa = (struct sockaddr *)saptr; + unsigned int salen = sa->sa_len; + if (!salen) + break; + + // Skip missing fields in rtm_addrs bit field + while ((rtm->rtm_addrs & 1) == 0) { + rtm->rtm_addrs >>= 1; + ++which; + if (which > 6) + break; + } + if (which > 6) + break; + + rtm->rtm_addrs >>= 1; + switch(which++) { + case 0: + //printf("RTA_DST\n"); + if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; + if ((sin6->sin6_addr.s6_addr[0] == 0xfe)&&((sin6->sin6_addr.s6_addr[1] & 0xc0) == 0x80)) { + // BSD uses this fucking strange in-band signaling method to encode device scope IDs for IPv6 addresses... probably a holdover from very early versions of the spec. + unsigned int interfaceIndex = ((((unsigned int)sin6->sin6_addr.s6_addr[2]) << 8) & 0xff) | (((unsigned int)sin6->sin6_addr.s6_addr[3]) & 0xff); + sin6->sin6_addr.s6_addr[2] = 0; + sin6->sin6_addr.s6_addr[3] = 0; + if (!sin6->sin6_scope_id) + sin6->sin6_scope_id = interfaceIndex; + } + } + sa_t = *sa; + break; + case 1: + //printf("RTA_GATEWAY\n"); + switch(sa->sa_family) { + case AF_LINK: + deviceIndex = (int)((const struct sockaddr_dl *)sa)->sdl_index; + break; + case AF_INET: + case AF_INET6: + sa_v = *sa; + break; + } + break; + case 2: { + //printf("RTA_NETMASK\n"); + if (sa_t.ss_family == AF_INET6) { + salen = sizeof(struct sockaddr_in6); + unsigned int bits = 0; + for(int i=0;i<16;++i) { + unsigned char c = (unsigned char)((const struct sockaddr_in6 *)sa)->sin6_addr.s6_addr[i]; + if (c == 0xff) + bits += 8; + else break; + } + sa_t.setPort(bits); + } else if (sa_t.ss_family == AF_INET) { + salen = sizeof(struct sockaddr_in); + sa_t.setPort((unsigned int)Utils::countBits((uint32_t)((const struct sockaddr_in *)sa)->sin_addr.s_addr)); + } + } break; + /* + case 3: + //printf("RTA_GENMASK\n"); + break; + case 4: + //printf("RTA_IFP\n"); + break; + case 5: + //printf("RTA_IFA\n"); + break; + case 6: + //printf("RTA_AUTHOR\n"); + break; + */ + } + + saptr += salen; + } + + if (((contains)&&(sa_t.containsAddress(target)))||(sa_t == target)) { + rtes.push_back(_RTE()); + rtes.back().target = sa_t; + rtes.back().via = sa_v; + if (deviceIndex >= 0) { + if_indextoname(deviceIndex,rtes.back().device); + } else { + rtes.back().device[0] = (char)0; + } + rtes.back().metric = ((int)rtm->rtm_rmx.rmx_hopcount < 0) ? 0 : (int)rtm->rtm_rmx.rmx_hopcount; + } + } + + next = saend; + } + } + + ::free(buf); + } + } + + return rtes; +} + +static void _routeCmd(const char *op,const InetAddress &target,const InetAddress &via,const char *ifscope,const char *localInterface) +{ + //printf("route %s %s %s %s %s\n",op,target.toString().c_str(),(via) ? via.toString().c_str() : "(null)",(ifscope) ? ifscope : "(null)",(localInterface) ? localInterface : "(null)"); + long p = (long)fork(); + if (p > 0) { + int exitcode = -1; + ::waitpid(p,&exitcode,0); + } else if (p == 0) { + ::close(STDOUT_FILENO); + ::close(STDERR_FILENO); + if (via) { + if ((ifscope)&&(ifscope[0])) { + ::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,"-ifscope",ifscope,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString().c_str(),via.toIpString().c_str(),(const char *)0); + } else { + ::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString().c_str(),via.toIpString().c_str(),(const char *)0); + } + } else if ((localInterface)&&(localInterface[0])) { + if ((ifscope)&&(ifscope[0])) { + ::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,"-ifscope",ifscope,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString().c_str(),"-interface",localInterface,(const char *)0); + } else { + ::execl(ZT_BSD_ROUTE_CMD,ZT_BSD_ROUTE_CMD,op,((target.ss_family == AF_INET6) ? "-inet6" : "-inet"),target.toString().c_str(),"-interface",localInterface,(const char *)0); + } + } + ::_exit(-1); + } +} + +#endif // __BSD__ ------------------------------------------------------------ + +#ifdef __LINUX__ // ---------------------------------------------------------- +#define ZT_ROUTING_SUPPORT_FOUND 1 + +static void _routeCmd(const char *op,const InetAddress &target,const InetAddress &via,const char *localInterface) +{ + long p = (long)fork(); + if (p > 0) { + int exitcode = -1; + ::waitpid(p,&exitcode,0); + } else if (p == 0) { + ::close(STDOUT_FILENO); + ::close(STDERR_FILENO); + if (via) { + ::execl(ZT_LINUX_IP_COMMAND,ZT_LINUX_IP_COMMAND,(target.ss_family == AF_INET6) ? "-6" : "-4","route",op,target.toString().c_str(),"via",via.toIpString().c_str(),(const char *)0); + ::execl(ZT_LINUX_IP_COMMAND_2,ZT_LINUX_IP_COMMAND_2,(target.ss_family == AF_INET6) ? "-6" : "-4","route",op,target.toString().c_str(),"via",via.toIpString().c_str(),(const char *)0); + } else if ((localInterface)&&(localInterface[0])) { + ::execl(ZT_LINUX_IP_COMMAND,ZT_LINUX_IP_COMMAND,(target.ss_family == AF_INET6) ? "-6" : "-4","route",op,target.toString().c_str(),"dev",localInterface,(const char *)0); + ::execl(ZT_LINUX_IP_COMMAND_2,ZT_LINUX_IP_COMMAND_2,(target.ss_family == AF_INET6) ? "-6" : "-4","route",op,target.toString().c_str(),"dev",localInterface,(const char *)0); + } + ::_exit(-1); + } +} + +#endif // __LINUX__ ---------------------------------------------------------- + +#ifdef __WINDOWS__ // -------------------------------------------------------- +#define ZT_ROUTING_SUPPORT_FOUND 1 + +static bool _winRoute(bool del,const NET_LUID &interfaceLuid,const NET_IFINDEX &interfaceIndex,const InetAddress &target,const InetAddress &via) +{ + MIB_IPFORWARD_ROW2 rtrow; + InitializeIpForwardEntry(&rtrow); + rtrow.InterfaceLuid.Value = interfaceLuid.Value; + rtrow.InterfaceIndex = interfaceIndex; + if (target.ss_family == AF_INET) { + rtrow.DestinationPrefix.Prefix.si_family = AF_INET; + rtrow.DestinationPrefix.Prefix.Ipv4.sin_family = AF_INET; + rtrow.DestinationPrefix.Prefix.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast(&target)->sin_addr.S_un.S_addr; + if (via.ss_family == AF_INET) { + rtrow.NextHop.si_family = AF_INET; + rtrow.NextHop.Ipv4.sin_family = AF_INET; + rtrow.NextHop.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast(&via)->sin_addr.S_un.S_addr; + } + } else if (target.ss_family == AF_INET6) { + rtrow.DestinationPrefix.Prefix.si_family = AF_INET6; + rtrow.DestinationPrefix.Prefix.Ipv6.sin6_family = AF_INET6; + memcpy(rtrow.DestinationPrefix.Prefix.Ipv6.sin6_addr.u.Byte,reinterpret_cast(&target)->sin6_addr.u.Byte,16); + if (via.ss_family == AF_INET6) { + rtrow.NextHop.si_family = AF_INET6; + rtrow.NextHop.Ipv6.sin6_family = AF_INET6; + memcpy(rtrow.NextHop.Ipv6.sin6_addr.u.Byte,reinterpret_cast(&via)->sin6_addr.u.Byte,16); + } + } else { + return false; + } + rtrow.DestinationPrefix.PrefixLength = target.netmaskBits(); + rtrow.SitePrefixLength = rtrow.DestinationPrefix.PrefixLength; + rtrow.ValidLifetime = 0xffffffff; + rtrow.PreferredLifetime = 0xffffffff; + rtrow.Metric = -1; + rtrow.Protocol = MIB_IPPROTO_NETMGMT; + rtrow.Loopback = FALSE; + rtrow.AutoconfigureAddress = FALSE; + rtrow.Publish = FALSE; + rtrow.Immortal = FALSE; + rtrow.Age = 0; + rtrow.Origin = NlroManual; + if (del) { + return (DeleteIpForwardEntry2(&rtrow) == NO_ERROR); + } else { + NTSTATUS r = CreateIpForwardEntry2(&rtrow); + if (r == NO_ERROR) { + return true; + } else if (r == ERROR_OBJECT_ALREADY_EXISTS) { + return (SetIpForwardEntry2(&rtrow) == NO_ERROR); + } else { + return false; + } + } +} + +static bool _winHasRoute(const NET_LUID &interfaceLuid, const NET_IFINDEX &interfaceIndex, const InetAddress &target, const InetAddress &via) +{ + MIB_IPFORWARD_ROW2 rtrow; + InitializeIpForwardEntry(&rtrow); + rtrow.InterfaceLuid.Value = interfaceLuid.Value; + rtrow.InterfaceIndex = interfaceIndex; + if (target.ss_family == AF_INET) { + rtrow.DestinationPrefix.Prefix.si_family = AF_INET; + rtrow.DestinationPrefix.Prefix.Ipv4.sin_family = AF_INET; + rtrow.DestinationPrefix.Prefix.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast(&target)->sin_addr.S_un.S_addr; + if (via.ss_family == AF_INET) { + rtrow.NextHop.si_family = AF_INET; + rtrow.NextHop.Ipv4.sin_family = AF_INET; + rtrow.NextHop.Ipv4.sin_addr.S_un.S_addr = reinterpret_cast(&via)->sin_addr.S_un.S_addr; + } + } else if (target.ss_family == AF_INET6) { + rtrow.DestinationPrefix.Prefix.si_family = AF_INET6; + rtrow.DestinationPrefix.Prefix.Ipv6.sin6_family = AF_INET6; + memcpy(rtrow.DestinationPrefix.Prefix.Ipv6.sin6_addr.u.Byte, reinterpret_cast(&target)->sin6_addr.u.Byte, 16); + if (via.ss_family == AF_INET6) { + rtrow.NextHop.si_family = AF_INET6; + rtrow.NextHop.Ipv6.sin6_family = AF_INET6; + memcpy(rtrow.NextHop.Ipv6.sin6_addr.u.Byte, reinterpret_cast(&via)->sin6_addr.u.Byte, 16); + } + } else { + return false; + } + rtrow.DestinationPrefix.PrefixLength = target.netmaskBits(); + rtrow.SitePrefixLength = rtrow.DestinationPrefix.PrefixLength; + return (GetIpForwardEntry2(&rtrow) == NO_ERROR); +} + +#endif // __WINDOWS__ -------------------------------------------------------- + +#ifndef ZT_ROUTING_SUPPORT_FOUND +#error "ManagedRoute.cpp has no support for managing routes on this platform! You'll need to check and see if one of the existing ones will work and make sure proper defines are set, or write one. Please do a GitHub pull request if you do this for a new OS." +#endif + +} // anonymous namespace + +/* Linux NOTE: for default route override, some Linux distributions will + * require a change to the rp_filter parameter. A value of '1' will prevent + * default route override from working properly. + * + * sudo sysctl -w net.ipv4.conf.all.rp_filter=2 + * + * Add to /etc/sysctl.conf or /etc/sysctl.d/... to make permanent. + * + * This is true of CentOS/RHEL 6+ and possibly others. This is because + * Linux default route override implies asymmetric routes, which then + * trigger Linux's "martian packet" filter. */ + +bool ManagedRoute::sync() +{ +#ifdef __WINDOWS__ + NET_LUID interfaceLuid; + interfaceLuid.Value = (ULONG64)Utils::hexStrToU64(_device); // on Windows we use the hex LUID as the "interface name" for ManagedRoute + NET_IFINDEX interfaceIndex = -1; + if (ConvertInterfaceLuidToIndex(&interfaceLuid,&interfaceIndex) != NO_ERROR) + return false; +#endif + + InetAddress leftt,rightt; + if (_target.netmaskBits() == 0) // bifurcate only the default route + _forkTarget(_target,leftt,rightt); + else leftt = _target; + +#ifdef __BSD__ // ------------------------------------------------------------ + + // Find lowest metric system route that this route should override (if any) + InetAddress newSystemVia; + char newSystemDevice[128]; + newSystemDevice[0] = (char)0; + int systemMetric = 9999999; + std::vector<_RTE> rtes(_getRTEs(_target,false)); + for(std::vector<_RTE>::iterator r(rtes.begin());r!=rtes.end();++r) { + if (r->via) { + if ( ((!newSystemVia)||(r->metric < systemMetric)) && (strcmp(r->device,_device) != 0) ) { + newSystemVia = r->via; + Utils::scopy(newSystemDevice,sizeof(newSystemDevice),r->device); + systemMetric = r->metric; + } + } + } + + // Get device corresponding to route if we don't have that already + if ((newSystemVia)&&(!newSystemDevice[0])) { + rtes = _getRTEs(newSystemVia,true); + for(std::vector<_RTE>::iterator r(rtes.begin());r!=rtes.end();++r) { + if ( (r->device[0]) && (strcmp(r->device,_device) != 0) ) { + Utils::scopy(newSystemDevice,sizeof(newSystemDevice),r->device); + break; + } + } + } + if (!newSystemDevice[0]) + newSystemVia.zero(); + + // Shadow system route if it exists, also delete any obsolete shadows + // and replace them with the new state. sync() is called periodically to + // allow us to do that if underlying connectivity changes. + if ((_systemVia != newSystemVia)||(strcmp(_systemDevice,newSystemDevice) != 0)) { + if (_systemVia) { + _routeCmd("delete",leftt,_systemVia,_systemDevice,(const char *)0); + if (rightt) + _routeCmd("delete",rightt,_systemVia,_systemDevice,(const char *)0); + } + + _systemVia = newSystemVia; + Utils::scopy(_systemDevice,sizeof(_systemDevice),newSystemDevice); + + if (_systemVia) { + _routeCmd("add",leftt,_systemVia,_systemDevice,(const char *)0); + _routeCmd("change",leftt,_systemVia,_systemDevice,(const char *)0); + if (rightt) { + _routeCmd("add",rightt,_systemVia,_systemDevice,(const char *)0); + _routeCmd("change",rightt,_systemVia,_systemDevice,(const char *)0); + } + } + } + + if (!_applied.count(leftt)) { + _applied[leftt] = false; // not ifscoped + _routeCmd("add",leftt,_via,(const char *)0,(_via) ? (const char *)0 : _device); + _routeCmd("change",leftt,_via,(const char *)0,(_via) ? (const char *)0 : _device); + } + if ((rightt)&&(!_applied.count(rightt))) { + _applied[rightt] = false; // not ifscoped + _routeCmd("add",rightt,_via,(const char *)0,(_via) ? (const char *)0 : _device); + _routeCmd("change",rightt,_via,(const char *)0,(_via) ? (const char *)0 : _device); + } + +#endif // __BSD__ ------------------------------------------------------------ + +#ifdef __LINUX__ // ---------------------------------------------------------- + + if (!_applied.count(leftt)) { + _applied[leftt] = false; // boolean unused + _routeCmd("replace",leftt,_via,(_via) ? (const char *)0 : _device); + } + if ((rightt)&&(!_applied.count(rightt))) { + _applied[rightt] = false; // boolean unused + _routeCmd("replace",rightt,_via,(_via) ? (const char *)0 : _device); + } + +#endif // __LINUX__ ---------------------------------------------------------- + +#ifdef __WINDOWS__ // -------------------------------------------------------- + + if ( (!_applied.count(leftt)) || (!_winHasRoute(interfaceLuid,interfaceIndex,leftt,_via)) ) { + _applied[leftt] = false; // boolean unused + _winRoute(false,interfaceLuid,interfaceIndex,leftt,_via); + } + if ( (rightt) && ( (!_applied.count(rightt)) || (!_winHasRoute(interfaceLuid,interfaceIndex,rightt,_via)) ) ) { + _applied[rightt] = false; // boolean unused + _winRoute(false,interfaceLuid,interfaceIndex,rightt,_via); + } + +#endif // __WINDOWS__ -------------------------------------------------------- + + return true; +} + +void ManagedRoute::remove() +{ +#ifdef __WINDOWS__ + NET_LUID interfaceLuid; + interfaceLuid.Value = (ULONG64)Utils::hexStrToU64(_device); // on Windows we use the hex LUID as the "interface name" for ManagedRoute + NET_IFINDEX interfaceIndex = -1; + if (ConvertInterfaceLuidToIndex(&interfaceLuid,&interfaceIndex) != NO_ERROR) + return; +#endif + +#ifdef __BSD__ + if (_systemVia) { + InetAddress leftt,rightt; + _forkTarget(_target,leftt,rightt); + _routeCmd("delete",leftt,_systemVia,_systemDevice,(const char *)0); + if (rightt) + _routeCmd("delete",rightt,_systemVia,_systemDevice,(const char *)0); + } +#endif // __BSD__ ------------------------------------------------------------ + + for(std::map::iterator r(_applied.begin());r!=_applied.end();++r) { +#ifdef __BSD__ // ------------------------------------------------------------ + _routeCmd("delete",r->first,_via,r->second ? _device : (const char *)0,(_via) ? (const char *)0 : _device); +#endif // __BSD__ ------------------------------------------------------------ + +#ifdef __LINUX__ // ---------------------------------------------------------- + _routeCmd("del",r->first,_via,(_via) ? (const char *)0 : _device); +#endif // __LINUX__ ---------------------------------------------------------- + +#ifdef __WINDOWS__ // -------------------------------------------------------- + _winRoute(true,interfaceLuid,interfaceIndex,r->first,_via); +#endif // __WINDOWS__ -------------------------------------------------------- + } + + _target.zero(); + _via.zero(); + _systemVia.zero(); + _device[0] = (char)0; + _systemDevice[0] = (char)0; + _applied.clear(); +} + +} // namespace ZeroTier diff --git a/osdep/ManagedRoute.hpp b/osdep/ManagedRoute.hpp new file mode 100644 index 0000000..fd77a79 --- /dev/null +++ b/osdep/ManagedRoute.hpp @@ -0,0 +1,80 @@ +#ifndef ZT_MANAGEDROUTE_HPP +#define ZT_MANAGEDROUTE_HPP + +#include +#include + +#include "../node/InetAddress.hpp" +#include "../node/Utils.hpp" +#include "../node/SharedPtr.hpp" +#include "../node/AtomicCounter.hpp" +#include "../node/NonCopyable.hpp" + +#include +#include +#include + +namespace ZeroTier { + +/** + * A ZT-managed route that used C++ RAII semantics to automatically clean itself up on deallocate + */ +class ManagedRoute : NonCopyable +{ + friend class SharedPtr; + +public: + ManagedRoute(const InetAddress &target,const InetAddress &via,const char *device) + { + _target = target; + _via = via; + if (via.ss_family == AF_INET) + _via.setPort(32); + else if (via.ss_family == AF_INET6) + _via.setPort(128); + Utils::scopy(_device,sizeof(_device),device); + _systemDevice[0] = (char)0; + } + + ~ManagedRoute() + { + this->remove(); + } + + /** + * Set or update currently set route + * + * This must be called periodically for routes that shadow others so that + * shadow routes can be updated. In some cases it has no effect + * + * @return True if route add/update was successful + */ + bool sync(); + + /** + * Remove and clear this ManagedRoute + * + * This does nothing if this ManagedRoute is not set or has already been + * removed. If this is not explicitly called it is called automatically on + * destruct. + */ + void remove(); + + inline const InetAddress &target() const { return _target; } + inline const InetAddress &via() const { return _via; } + inline const char *device() const { return _device; } + +private: + InetAddress _target; + InetAddress _via; + InetAddress _systemVia; // for route overrides + std::map _applied; // routes currently applied + char _device[128]; + char _systemDevice[128]; // for route overrides + + AtomicCounter __refCount; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/NeighborDiscovery.cpp b/osdep/NeighborDiscovery.cpp new file mode 100644 index 0000000..4f63631 --- /dev/null +++ b/osdep/NeighborDiscovery.cpp @@ -0,0 +1,264 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include "NeighborDiscovery.hpp" +#include "OSUtils.hpp" + +#include "../include/ZeroTierOne.h" + +#include + +namespace ZeroTier { + +uint16_t calc_checksum (uint16_t *addr, int len) +{ + int count = len; + uint32_t sum = 0; + uint16_t answer = 0; + + // Sum up 2-byte values until none or only one byte left. + while (count > 1) { + sum += *(addr++); + count -= 2; + } + + // Add left-over byte, if any. + if (count > 0) { + sum += *(uint8_t *) addr; + } + + // Fold 32-bit sum into 16 bits; we lose information by doing this, + // increasing the chances of a collision. + // sum = (lower 16 bits) + (upper 16 bits shifted right 16 bits) + while (sum >> 16) { + sum = (sum & 0xffff) + (sum >> 16); + } + + // Checksum is one's compliment of sum. + answer = ~sum; + + return (answer); +} + +struct _pseudo_header { + uint8_t sourceAddr[16]; + uint8_t targetAddr[16]; + uint32_t length; + uint8_t zeros[3]; + uint8_t next; // 58 +}; + +struct _option { + _option(int optionType) + : type(optionType) + , length(8) + { + memset(mac, 0, sizeof(mac)); + } + + uint8_t type; + uint8_t length; + uint8_t mac[6]; +}; + +struct _neighbor_solicitation { + _neighbor_solicitation() + : type(135) + , code(0) + , checksum(0) + , option(1) + { + memset(&reserved, 0, sizeof(reserved)); + memset(target, 0, sizeof(target)); + } + + void calculateChecksum(const sockaddr_storage &sourceIp, const sockaddr_storage &destIp) { + _pseudo_header ph; + memset(&ph, 0, sizeof(_pseudo_header)); + const sockaddr_in6 *src = (const sockaddr_in6*)&sourceIp; + const sockaddr_in6 *dest = (const sockaddr_in6*)&destIp; + + memcpy(ph.sourceAddr, &src->sin6_addr, sizeof(struct in6_addr)); + memcpy(ph.targetAddr, &dest->sin6_addr, sizeof(struct in6_addr)); + ph.next = 58; + ph.length = htonl(sizeof(_neighbor_solicitation)); + + size_t len = sizeof(_pseudo_header) + sizeof(_neighbor_solicitation); + uint8_t *tmp = (uint8_t*)malloc(len); + memcpy(tmp, &ph, sizeof(_pseudo_header)); + memcpy(tmp+sizeof(_pseudo_header), this, sizeof(_neighbor_solicitation)); + + checksum = calc_checksum((uint16_t*)tmp, (int)len); + + free(tmp); + tmp = NULL; + } + + uint8_t type; // 135 + uint8_t code; // 0 + uint16_t checksum; + uint32_t reserved; + uint8_t target[16]; + _option option; +}; + +struct _neighbor_advertisement { + _neighbor_advertisement() + : type(136) + , code(0) + , checksum(0) + , rso(0x40) + , option(2) + { + memset(padding, 0, sizeof(padding)); + memset(target, 0, sizeof(target)); + } + + void calculateChecksum(const sockaddr_storage &sourceIp, const sockaddr_storage &destIp) { + _pseudo_header ph; + memset(&ph, 0, sizeof(_pseudo_header)); + const sockaddr_in6 *src = (const sockaddr_in6*)&sourceIp; + const sockaddr_in6 *dest = (const sockaddr_in6*)&destIp; + + memcpy(ph.sourceAddr, &src->sin6_addr, sizeof(struct in6_addr)); + memcpy(ph.targetAddr, &dest->sin6_addr, sizeof(struct in6_addr)); + ph.next = 58; + ph.length = htonl(sizeof(_neighbor_advertisement)); + + size_t len = sizeof(_pseudo_header) + sizeof(_neighbor_advertisement); + uint8_t *tmp = (uint8_t*)malloc(len); + memcpy(tmp, &ph, sizeof(_pseudo_header)); + memcpy(tmp+sizeof(_pseudo_header), this, sizeof(_neighbor_advertisement)); + + checksum = calc_checksum((uint16_t*)tmp, (int)len); + + free(tmp); + tmp = NULL; + } + + uint8_t type; // 136 + uint8_t code; // 0 + uint16_t checksum; + uint8_t rso; + uint8_t padding[3]; + uint8_t target[16]; + _option option; +}; + +NeighborDiscovery::NeighborDiscovery() + : _cache(256) + , _lastCleaned(OSUtils::now()) +{} + +void NeighborDiscovery::addLocal(const sockaddr_storage &address, const MAC &mac) +{ + _NDEntry &e = _cache[InetAddress(address)]; + e.lastQuerySent = 0; + e.lastResponseReceived = 0; + e.mac = mac; + e.local = true; +} + +void NeighborDiscovery::remove(const sockaddr_storage &address) +{ + _cache.erase(InetAddress(address)); +} + +sockaddr_storage NeighborDiscovery::processIncomingND(const uint8_t *nd, unsigned int len, const sockaddr_storage &localIp, uint8_t *response, unsigned int &responseLen, MAC &responseDest) +{ + assert(sizeof(_neighbor_solicitation) == 28); + assert(sizeof(_neighbor_advertisement) == 32); + + const uint64_t now = OSUtils::now(); + sockaddr_storage ip = ZT_SOCKADDR_NULL; + + if (len >= sizeof(_neighbor_solicitation) && nd[0] == 0x87) { + // respond to Neighbor Solicitation request for local address + _neighbor_solicitation solicitation; + memcpy(&solicitation, nd, len); + InetAddress targetAddress(solicitation.target, 16, 0); + _NDEntry *targetEntry = _cache.get(targetAddress); + if (targetEntry && targetEntry->local) { + _neighbor_advertisement adv; + targetEntry->mac.copyTo(adv.option.mac, 6); + memcpy(adv.target, solicitation.target, 16); + adv.calculateChecksum(localIp, targetAddress); + memcpy(response, &adv, sizeof(_neighbor_advertisement)); + responseLen = sizeof(_neighbor_advertisement); + responseDest.setTo(solicitation.option.mac, 6); + } + } else if (len >= sizeof(_neighbor_advertisement) && nd[0] == 0x88) { + _neighbor_advertisement adv; + memcpy(&adv, nd, len); + InetAddress responseAddress(adv.target, 16, 0); + _NDEntry *queryEntry = _cache.get(responseAddress); + if(queryEntry && !queryEntry->local && (now - queryEntry->lastQuerySent <= ZT_ND_QUERY_MAX_TTL)) { + queryEntry->lastResponseReceived = now; + queryEntry->mac.setTo(adv.option.mac, 6); + ip = responseAddress; + } + } + + if ((now - _lastCleaned) >= ZT_ND_EXPIRE) { + _lastCleaned = now; + Hashtable::Iterator i(_cache); + InetAddress *k = NULL; + _NDEntry *v = NULL; + while (i.next(k, v)) { + if(!v->local && (now - v->lastResponseReceived) >= ZT_ND_EXPIRE) { + _cache.erase(*k); + } + } + } + + return ip; +} + +MAC NeighborDiscovery::query(const MAC &localMac, const sockaddr_storage &localIp, const sockaddr_storage &targetIp, uint8_t *query, unsigned int &queryLen, MAC &queryDest) +{ + const uint64_t now = OSUtils::now(); + + InetAddress localAddress(localIp); + localAddress.setPort(0); + InetAddress targetAddress(targetIp); + targetAddress.setPort(0); + + _NDEntry &e = _cache[targetAddress]; + + if ( (e.mac && ((now - e.lastResponseReceived) >= (ZT_ND_EXPIRE / 3))) || + (!e.mac && ((now - e.lastQuerySent) >= ZT_ND_QUERY_INTERVAL))) { + e.lastQuerySent = now; + + _neighbor_solicitation ns; + memcpy(ns.target, targetAddress.rawIpData(), 16); + localMac.copyTo(ns.option.mac, 6); + ns.calculateChecksum(localIp, targetIp); + if (e.mac) { + queryDest = e.mac; + } else { + queryDest = (uint64_t)0xffffffffffffULL; + } + } else { + queryLen = 0; + queryDest.zero(); + } + + return e.mac; +} + +} diff --git a/osdep/NeighborDiscovery.hpp b/osdep/NeighborDiscovery.hpp new file mode 100644 index 0000000..47831bd --- /dev/null +++ b/osdep/NeighborDiscovery.hpp @@ -0,0 +1,76 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_NEIGHBORDISCOVERY_HPP +#define ZT_NEIGHBORDISCOVERY_HPP + +#include "../node/Hashtable.hpp" +#include "../node/MAC.hpp" +#include "../node/InetAddress.hpp" + + +#define ZT_ND_QUERY_INTERVAL 2000 + +#define ZT_ND_QUERY_MAX_TTL 5000 + +#define ZT_ND_EXPIRE 600000 + + +namespace ZeroTier { + +class NeighborDiscovery +{ +public: + NeighborDiscovery(); + + /** + * Set a local IP entry that we should respond to Neighbor Requests withPrefix64k + * + * @param mac Our local MAC address + * @param ip Our IPv6 address + */ + void addLocal(const sockaddr_storage &address, const MAC &mac); + + /** + * Delete a local IP entry or cached Neighbor entry + * + * @param address IPv6 address to remove + */ + void remove(const sockaddr_storage &address); + + sockaddr_storage processIncomingND(const uint8_t *nd, unsigned int len, const sockaddr_storage &localIp, uint8_t *response, unsigned int &responseLen, MAC &responseDest); + + MAC query(const MAC &localMac, const sockaddr_storage &localIp, const sockaddr_storage &targetIp, uint8_t *query, unsigned int &queryLen, MAC &queryDest); + +private: + struct _NDEntry + { + _NDEntry() : lastQuerySent(0), lastResponseReceived(0), mac(), local(false) {} + uint64_t lastQuerySent; + uint64_t lastResponseReceived; + MAC mac; + bool local; + }; + + Hashtable _cache; + uint64_t _lastCleaned; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp new file mode 100644 index 0000000..fd5efed --- /dev/null +++ b/osdep/OSUtils.cpp @@ -0,0 +1,470 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" + +#ifdef __UNIX_LIKE__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#ifdef __WINDOWS__ +#include +#include +#include +#include +#include +#endif + +#include "OSUtils.hpp" + +namespace ZeroTier { + +#ifdef __UNIX_LIKE__ +bool OSUtils::redirectUnixOutputs(const char *stdoutPath,const char *stderrPath) + throw() +{ + int fdout = ::open(stdoutPath,O_WRONLY|O_CREAT,0600); + if (fdout > 0) { + int fderr; + if (stderrPath) { + fderr = ::open(stderrPath,O_WRONLY|O_CREAT,0600); + if (fderr <= 0) { + ::close(fdout); + return false; + } + } else fderr = fdout; + ::close(STDOUT_FILENO); + ::close(STDERR_FILENO); + ::dup2(fdout,STDOUT_FILENO); + ::dup2(fderr,STDERR_FILENO); + return true; + } + return false; +} +#endif // __UNIX_LIKE__ + +std::vector OSUtils::listDirectory(const char *path,bool includeDirectories) +{ + std::vector r; + +#ifdef __WINDOWS__ + HANDLE hFind; + WIN32_FIND_DATAA ffd; + if ((hFind = FindFirstFileA((std::string(path) + "\\*").c_str(),&ffd)) != INVALID_HANDLE_VALUE) { + do { + if ( (strcmp(ffd.cFileName,".")) && (strcmp(ffd.cFileName,"..")) && (((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)||(((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)&&(includeDirectories))) ) + r.push_back(std::string(ffd.cFileName)); + } while (FindNextFileA(hFind,&ffd)); + FindClose(hFind); + } +#else + struct dirent de; + struct dirent *dptr; + DIR *d = opendir(path); + if (!d) + return r; + dptr = (struct dirent *)0; + for(;;) { + if (readdir_r(d,&de,&dptr)) + break; + if (dptr) { + if ((strcmp(dptr->d_name,"."))&&(strcmp(dptr->d_name,".."))&&((dptr->d_type != DT_DIR)||(includeDirectories))) + r.push_back(std::string(dptr->d_name)); + } else break; + } + closedir(d); +#endif + + return r; +} + +long OSUtils::cleanDirectory(const char *path,const uint64_t olderThan) +{ + long cleaned = 0; + +#ifdef __WINDOWS__ + HANDLE hFind; + WIN32_FIND_DATAA ffd; + LARGE_INTEGER date,adjust; + adjust.QuadPart = 11644473600000 * 10000; + char tmp[4096]; + if ((hFind = FindFirstFileA((std::string(path) + "\\*").c_str(),&ffd)) != INVALID_HANDLE_VALUE) { + do { + if ((strcmp(ffd.cFileName,"."))&&(strcmp(ffd.cFileName,".."))&&((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)) { + date.HighPart = ffd.ftLastWriteTime.dwHighDateTime; + date.LowPart = ffd.ftLastWriteTime.dwLowDateTime; + if (date.QuadPart > 0) { + date.QuadPart -= adjust.QuadPart; + if ((uint64_t)((date.QuadPart / 10000000) * 1000) < olderThan) { + Utils::snprintf(tmp, sizeof(tmp), "%s\\%s", path, ffd.cFileName); + if (DeleteFileA(tmp)) + ++cleaned; + } + } + } + } while (FindNextFileA(hFind,&ffd)); + FindClose(hFind); + } +#else + struct dirent de; + struct dirent *dptr; + struct stat st; + char tmp[4096]; + DIR *d = opendir(path); + if (!d) + return -1; + dptr = (struct dirent *)0; + for(;;) { + if (readdir_r(d,&de,&dptr)) + break; + if (dptr) { + if ((strcmp(dptr->d_name,"."))&&(strcmp(dptr->d_name,".."))&&(dptr->d_type == DT_REG)) { + Utils::snprintf(tmp,sizeof(tmp),"%s/%s",path,dptr->d_name); + if (stat(tmp,&st) == 0) { + uint64_t mt = (uint64_t)(st.st_mtime); + if ((mt > 0)&&((mt * 1000) < olderThan)) { + if (unlink(tmp) == 0) + ++cleaned; + } + } + } + } else break; + } + closedir(d); +#endif + + return cleaned; +} + +bool OSUtils::rmDashRf(const char *path) +{ +#ifdef __WINDOWS__ + HANDLE hFind; + WIN32_FIND_DATAA ffd; + if ((hFind = FindFirstFileA((std::string(path) + "\\*").c_str(),&ffd)) != INVALID_HANDLE_VALUE) { + do { + if ((strcmp(ffd.cFileName,".") != 0)&&(strcmp(ffd.cFileName,"..") != 0)) { + if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + if (DeleteFileA((std::string(path) + ZT_PATH_SEPARATOR_S + ffd.cFileName).c_str()) == FALSE) + return false; + } else { + if (!rmDashRf((std::string(path) + ZT_PATH_SEPARATOR_S + ffd.cFileName).c_str())) + return false; + } + } + } while (FindNextFileA(hFind,&ffd)); + FindClose(hFind); + } + return (RemoveDirectoryA(path) != FALSE); +#else + struct dirent de; + struct dirent *dptr; + DIR *d = opendir(path); + if (!d) + return true; + dptr = (struct dirent *)0; + for(;;) { + if (readdir_r(d,&de,&dptr) != 0) + break; + if (!dptr) + break; + if ((strcmp(dptr->d_name,".") != 0)&&(strcmp(dptr->d_name,"..") != 0)&&(strlen(dptr->d_name) > 0)) { + std::string p(path); + p.push_back(ZT_PATH_SEPARATOR); + p.append(dptr->d_name); + if (unlink(p.c_str()) != 0) { // unlink first will remove symlinks instead of recursing them + if (!rmDashRf(p.c_str())) + return false; + } + } + } + closedir(d); + return (rmdir(path) == 0); +#endif +} + +void OSUtils::lockDownFile(const char *path,bool isDir) +{ +#ifdef __UNIX_LIKE__ + chmod(path,isDir ? 0700 : 0600); +#else +#ifdef __WINDOWS__ + { + STARTUPINFOA startupInfo; + PROCESS_INFORMATION processInfo; + + startupInfo.cb = sizeof(startupInfo); + memset(&startupInfo,0,sizeof(STARTUPINFOA)); + memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); + if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\icacls.exe \"") + path + "\" /inheritance:d /Q").c_str(),NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&startupInfo,&processInfo)) { + WaitForSingleObject(processInfo.hProcess,INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + + startupInfo.cb = sizeof(startupInfo); + memset(&startupInfo,0,sizeof(STARTUPINFOA)); + memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); + if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\icacls.exe \"") + path + "\" /remove *S-1-5-32-545 /Q").c_str(),NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL,&startupInfo,&processInfo)) { + WaitForSingleObject(processInfo.hProcess,INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + } +#endif +#endif +} + +uint64_t OSUtils::getLastModified(const char *path) +{ + struct stat s; + if (stat(path,&s)) + return 0; + return (((uint64_t)s.st_mtime) * 1000ULL); +} + +bool OSUtils::fileExists(const char *path,bool followLinks) +{ + struct stat s; +#ifdef __UNIX_LIKE__ + if (!followLinks) + return (lstat(path,&s) == 0); +#endif + return (stat(path,&s) == 0); +} + +int64_t OSUtils::getFileSize(const char *path) +{ + struct stat s; + if (stat(path,&s)) + return -1; +#ifdef __WINDOWS__ + return s.st_size; +#else + if (S_ISREG(s.st_mode)) + return s.st_size; +#endif + return -1; +} + +bool OSUtils::readFile(const char *path,std::string &buf) +{ + char tmp[1024]; + FILE *f = fopen(path,"rb"); + if (f) { + for(;;) { + long n = (long)fread(tmp,1,sizeof(tmp),f); + if (n > 0) + buf.append(tmp,n); + else break; + } + fclose(f); + return true; + } + return false; +} + +bool OSUtils::writeFile(const char *path,const void *buf,unsigned int len) +{ + FILE *f = fopen(path,"wb"); + if (f) { + if ((long)fwrite(buf,1,len,f) != (long)len) { + fclose(f); + return false; + } else { + fclose(f); + return true; + } + } + return false; +} + +std::vector OSUtils::split(const char *s,const char *const sep,const char *esc,const char *quot) +{ + std::vector fields; + std::string buf; + + if (!esc) + esc = ""; + if (!quot) + quot = ""; + + bool escapeState = false; + char quoteState = 0; + while (*s) { + if (escapeState) { + escapeState = false; + buf.push_back(*s); + } else if (quoteState) { + if (*s == quoteState) { + quoteState = 0; + fields.push_back(buf); + buf.clear(); + } else buf.push_back(*s); + } else { + const char *quotTmp; + if (strchr(esc,*s)) + escapeState = true; + else if ((buf.size() <= 0)&&((quotTmp = strchr(quot,*s)))) + quoteState = *quotTmp; + else if (strchr(sep,*s)) { + if (buf.size() > 0) { + fields.push_back(buf); + buf.clear(); + } // else skip runs of seperators + } else buf.push_back(*s); + } + ++s; + } + + if (buf.size()) + fields.push_back(buf); + + return fields; +} + +std::string OSUtils::platformDefaultHomePath() +{ +#ifdef __UNIX_LIKE__ + +#ifdef __APPLE__ + // /Library/... on Apple + return std::string("/Library/Application Support/ZeroTier/One"); +#else + +#ifdef __BSD__ + // BSD likes /var/db instead of /var/lib + return std::string("/var/db/zerotier-one"); +#else + // Use /var/lib for Linux and other *nix + return std::string("/var/lib/zerotier-one"); +#endif + +#endif + +#else // not __UNIX_LIKE__ + +#ifdef __WINDOWS__ + // Look up app data folder on Windows, e.g. C:\ProgramData\... + char buf[16384]; + if (SUCCEEDED(SHGetFolderPathA(NULL,CSIDL_COMMON_APPDATA,NULL,0,buf))) + return (std::string(buf) + "\\ZeroTier\\One"); + else return std::string("C:\\ZeroTier\\One"); +#else + + return (std::string(ZT_PATH_SEPARATOR_S) + "ZeroTier" + ZT_PATH_SEPARATOR_S + "One"); // UNKNOWN PLATFORM + +#endif + +#endif // __UNIX_LIKE__ or not... +} + +// Inline these massive JSON operations in one place only to reduce binary footprint and compile time +nlohmann::json OSUtils::jsonParse(const std::string &buf) { return nlohmann::json::parse(buf.c_str()); } +std::string OSUtils::jsonDump(const nlohmann::json &j) { return j.dump(1); } + +uint64_t OSUtils::jsonInt(const nlohmann::json &jv,const uint64_t dfl) +{ + try { + if (jv.is_number()) { + return (uint64_t)jv; + } else if (jv.is_string()) { + std::string s = jv; + return Utils::strToU64(s.c_str()); + } else if (jv.is_boolean()) { + return ((bool)jv ? 1ULL : 0ULL); + } + } catch ( ... ) {} + return dfl; +} + +bool OSUtils::jsonBool(const nlohmann::json &jv,const bool dfl) +{ + try { + if (jv.is_boolean()) { + return (bool)jv; + } else if (jv.is_number()) { + return ((uint64_t)jv > 0ULL); + } else if (jv.is_string()) { + std::string s = jv; + if (s.length() > 0) { + switch(s[0]) { + case 't': + case 'T': + case '1': + return true; + } + } + return false; + } + } catch ( ... ) {} + return dfl; +} + +std::string OSUtils::jsonString(const nlohmann::json &jv,const char *dfl) +{ + try { + if (jv.is_string()) { + return jv; + } else if (jv.is_number()) { + char tmp[64]; + Utils::snprintf(tmp,sizeof(tmp),"%llu",(uint64_t)jv); + return tmp; + } else if (jv.is_boolean()) { + return ((bool)jv ? std::string("1") : std::string("0")); + } + } catch ( ... ) {} + return std::string((dfl) ? dfl : ""); +} + +std::string OSUtils::jsonBinFromHex(const nlohmann::json &jv) +{ + std::string s(jsonString(jv,"")); + if (s.length() > 0) { + char *buf = new char[(s.length() / 2) + 1]; + try { + unsigned int l = Utils::unhex(s,buf,(unsigned int)s.length()); + std::string b(buf,l); + delete [] buf; + return b; + } catch ( ... ) { + delete [] buf; + } + } + return std::string(); +} + +// Used to convert HTTP header names to ASCII lower case +const unsigned char OSUtils::TOLOWER_TABLE[256] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, ' ', '!', '"', '#', '$', '%', '&', 0x27, '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; + +} // namespace ZeroTier diff --git a/osdep/OSUtils.hpp b/osdep/OSUtils.hpp new file mode 100644 index 0000000..b84d5d2 --- /dev/null +++ b/osdep/OSUtils.hpp @@ -0,0 +1,286 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_OSUTILS_HPP +#define ZT_OSUTILS_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/InetAddress.hpp" + +#ifdef __WINDOWS__ +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include "../ext/json/json.hpp" + +namespace ZeroTier { + +/** + * Miscellaneous utility functions and global constants + */ +class OSUtils +{ +public: +#ifdef __UNIX_LIKE__ + /** + * Close STDOUT_FILENO and STDERR_FILENO and replace them with output to given path + * + * This can be called after fork() and prior to exec() to suppress output + * from a subprocess, such as auto-update. + * + * @param stdoutPath Path to file to use for stdout + * @param stderrPath Path to file to use for stderr, or NULL for same as stdout (default) + * @return True on success + */ + static bool redirectUnixOutputs(const char *stdoutPath,const char *stderrPath = (const char *)0) + throw(); +#endif // __UNIX_LIKE__ + + /** + * Delete a file + * + * @param path Path to delete + * @return True if delete was successful + */ + static inline bool rm(const char *path) + throw() + { +#ifdef __WINDOWS__ + return (DeleteFileA(path) != FALSE); +#else + return (unlink(path) == 0); +#endif + } + static inline bool rm(const std::string &path) throw() { return rm(path.c_str()); } + + static inline bool mkdir(const char *path) + { +#ifdef __WINDOWS__ + if (::PathIsDirectoryA(path)) + return true; + return (::CreateDirectoryA(path,NULL) == TRUE); +#else + if (::mkdir(path,0755) != 0) + return (errno == EEXIST); + return true; +#endif + } + static inline bool mkdir(const std::string &path) throw() { return OSUtils::mkdir(path.c_str()); } + + /** + * List a directory's contents + * + * @param path Path to list + * @param includeDirectories If true, include directories as well as files + * @return Names of files in directory (without path prepended) + */ + static std::vector listDirectory(const char *path,bool includeDirectories = false); + + /** + * Clean a directory of files whose last modified time is older than this + * + * This ignores directories, symbolic links, and other special files. + * + * @param olderThan Last modified older than timestamp (ms since epoch) + * @return Number of cleaned files or negative on fatal error + */ + static long cleanDirectory(const char *path,const uint64_t olderThan); + + /** + * Delete a directory and all its files and subdirectories recursively + * + * @param path Path to delete + * @return True on success + */ + static bool rmDashRf(const char *path); + + /** + * Set modes on a file to something secure + * + * This locks a file so that only the owner can access it. What it actually + * does varies by platform. + * + * @param path Path to lock + * @param isDir True if this is a directory + */ + static void lockDownFile(const char *path,bool isDir); + + /** + * Get file last modification time + * + * Resolution is often only second, not millisecond, but the return is + * always in ms for comparison against now(). + * + * @param path Path to file to get time + * @return Last modification time in ms since epoch or 0 if not found + */ + static uint64_t getLastModified(const char *path); + + /** + * @param path Path to check + * @param followLinks Follow links (on platforms with that concept) + * @return True if file or directory exists at path location + */ + static bool fileExists(const char *path,bool followLinks = true); + + /** + * @param path Path to file + * @return File size or -1 if nonexistent or other failure + */ + static int64_t getFileSize(const char *path); + + /** + * Get IP (v4 and/or v6) addresses for a given host + * + * This is a blocking resolver. + * + * @param name Host name + * @return IP addresses in InetAddress sort order or empty vector if not found + */ + static std::vector resolve(const char *name); + + /** + * @return Current time in milliseconds since epoch + */ + static inline uint64_t now() + throw() + { +#ifdef __WINDOWS__ + FILETIME ft; + SYSTEMTIME st; + ULARGE_INTEGER tmp; + GetSystemTime(&st); + SystemTimeToFileTime(&st,&ft); + tmp.LowPart = ft.dwLowDateTime; + tmp.HighPart = ft.dwHighDateTime; + return ( ((tmp.QuadPart - 116444736000000000ULL) / 10000L) + st.wMilliseconds ); +#else + struct timeval tv; + gettimeofday(&tv,(struct timezone *)0); + return ( (1000ULL * (uint64_t)tv.tv_sec) + (uint64_t)(tv.tv_usec / 1000) ); +#endif + }; + + /** + * @return Current time in seconds since epoch, to the highest available resolution + */ + static inline double nowf() + throw() + { +#ifdef __WINDOWS__ + FILETIME ft; + SYSTEMTIME st; + ULARGE_INTEGER tmp; + GetSystemTime(&st); + SystemTimeToFileTime(&st,&ft); + tmp.LowPart = ft.dwLowDateTime; + tmp.HighPart = ft.dwHighDateTime; + return (((double)(tmp.QuadPart - 116444736000000000ULL)) / 10000000.0); +#else + struct timeval tv; + gettimeofday(&tv,(struct timezone *)0); + return ( ((double)tv.tv_sec) + (((double)tv.tv_usec) / 1000000.0) ); +#endif + } + + /** + * Read the full contents of a file into a string buffer + * + * The buffer isn't cleared, so if it already contains data the file's data will + * be appended. + * + * @param path Path of file to read + * @param buf Buffer to fill + * @return True if open and read successful + */ + static bool readFile(const char *path,std::string &buf); + + /** + * Write a block of data to disk, replacing any current file contents + * + * @param path Path to write + * @param buf Buffer containing data + * @param len Length of buffer + * @return True if entire file was successfully written + */ + static bool writeFile(const char *path,const void *buf,unsigned int len); + + /** + * Split a string by delimiter, with optional escape and quote characters + * + * @param s String to split + * @param sep One or more separators + * @param esc Zero or more escape characters + * @param quot Zero or more quote characters + * @return Vector of tokens + */ + static std::vector split(const char *s,const char *const sep,const char *esc,const char *quot); + + /** + * Write a block of data to disk, replacing any current file contents + * + * @param path Path to write + * @param s Data to write + * @return True if entire file was successfully written + */ + static inline bool writeFile(const char *path,const std::string &s) { return writeFile(path,s.data(),(unsigned int)s.length()); } + + /** + * @param c ASCII character to convert + * @return Lower case ASCII character or unchanged if not a letter + */ + static inline char toLower(char c) throw() { return (char)OSUtils::TOLOWER_TABLE[(unsigned long)c]; } + + /** + * @return Platform default ZeroTier One home path + */ + static std::string platformDefaultHomePath(); + + static nlohmann::json jsonParse(const std::string &buf); + static std::string jsonDump(const nlohmann::json &j); + static uint64_t jsonInt(const nlohmann::json &jv,const uint64_t dfl); + static bool jsonBool(const nlohmann::json &jv,const bool dfl); + static std::string jsonString(const nlohmann::json &jv,const char *dfl); + static std::string jsonBinFromHex(const nlohmann::json &jv); + +private: + static const unsigned char TOLOWER_TABLE[256]; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/OSXEthernetTap.cpp b/osdep/OSXEthernetTap.cpp new file mode 100644 index 0000000..f70908b --- /dev/null +++ b/osdep/OSXEthernetTap.cpp @@ -0,0 +1,679 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// OSX compile fix... in6_var defines this in a struct which namespaces it for C++ ... why?!? +struct prf_ra { + u_char onlink : 1; + u_char autonomous : 1; + u_char reserved : 6; +} prf_ra; + +#include +#include + +// These are KERNEL_PRIVATE... why? +#ifndef SIOCAUTOCONF_START +#define SIOCAUTOCONF_START _IOWR('i', 132, struct in6_ifreq) /* accept rtadvd on this interface */ +#endif +#ifndef SIOCAUTOCONF_STOP +#define SIOCAUTOCONF_STOP _IOWR('i', 133, struct in6_ifreq) /* stop accepting rtadv for this interface */ +#endif + +// -------------------------------------------------------------------------- +// -------------------------------------------------------------------------- +// This source is from: +// http://www.opensource.apple.com/source/Libinfo/Libinfo-406.17/gen.subproj/getifmaddrs.c?txt +// It's here because OSX 10.6 does not have this convenience function. + +#define SALIGN (sizeof(uint32_t) - 1) +#define SA_RLEN(sa) ((sa)->sa_len ? (((sa)->sa_len + SALIGN) & ~SALIGN) : \ +(SALIGN + 1)) +#define MAX_SYSCTL_TRY 5 +#define RTA_MASKS (RTA_GATEWAY | RTA_IFP | RTA_IFA) + +/* FreeBSD uses NET_RT_IFMALIST and RTM_NEWMADDR from */ +/* We can use NET_RT_IFLIST2 and RTM_NEWMADDR2 on Darwin */ +//#define DARWIN_COMPAT + +//#ifdef DARWIN_COMPAT +#define GIM_SYSCTL_MIB NET_RT_IFLIST2 +#define GIM_RTM_ADDR RTM_NEWMADDR2 +//#else +//#define GIM_SYSCTL_MIB NET_RT_IFMALIST +//#define GIM_RTM_ADDR RTM_NEWMADDR +//#endif + +// Not in 10.6 includes so use our own +struct _intl_ifmaddrs { + struct _intl_ifmaddrs *ifma_next; + struct sockaddr *ifma_name; + struct sockaddr *ifma_addr; + struct sockaddr *ifma_lladdr; +}; + +static inline int _intl_getifmaddrs(struct _intl_ifmaddrs **pif) +{ + int icnt = 1; + int dcnt = 0; + int ntry = 0; + size_t len; + size_t needed; + int mib[6]; + int i; + char *buf; + char *data; + char *next; + char *p; + struct ifma_msghdr2 *ifmam; + struct _intl_ifmaddrs *ifa, *ift; + struct rt_msghdr *rtm; + struct sockaddr *sa; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; /* protocol */ + mib[3] = 0; /* wildcard address family */ + mib[4] = GIM_SYSCTL_MIB; + mib[5] = 0; /* no flags */ + do { + if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) + return (-1); + if ((buf = (char *)malloc(needed)) == NULL) + return (-1); + if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) { + if (errno != ENOMEM || ++ntry >= MAX_SYSCTL_TRY) { + free(buf); + return (-1); + } + free(buf); + buf = NULL; + } + } while (buf == NULL); + + for (next = buf; next < buf + needed; next += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)(void *)next; + if (rtm->rtm_version != RTM_VERSION) + continue; + switch (rtm->rtm_type) { + case GIM_RTM_ADDR: + ifmam = (struct ifma_msghdr2 *)(void *)rtm; + if ((ifmam->ifmam_addrs & RTA_IFA) == 0) + break; + icnt++; + p = (char *)(ifmam + 1); + for (i = 0; i < RTAX_MAX; i++) { + if ((RTA_MASKS & ifmam->ifmam_addrs & + (1 << i)) == 0) + continue; + sa = (struct sockaddr *)(void *)p; + len = SA_RLEN(sa); + dcnt += len; + p += len; + } + break; + } + } + + data = (char *)malloc(sizeof(struct _intl_ifmaddrs) * icnt + dcnt); + if (data == NULL) { + free(buf); + return (-1); + } + + ifa = (struct _intl_ifmaddrs *)(void *)data; + data += sizeof(struct _intl_ifmaddrs) * icnt; + + memset(ifa, 0, sizeof(struct _intl_ifmaddrs) * icnt); + ift = ifa; + + for (next = buf; next < buf + needed; next += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)(void *)next; + if (rtm->rtm_version != RTM_VERSION) + continue; + + switch (rtm->rtm_type) { + case GIM_RTM_ADDR: + ifmam = (struct ifma_msghdr2 *)(void *)rtm; + if ((ifmam->ifmam_addrs & RTA_IFA) == 0) + break; + + p = (char *)(ifmam + 1); + for (i = 0; i < RTAX_MAX; i++) { + if ((RTA_MASKS & ifmam->ifmam_addrs & + (1 << i)) == 0) + continue; + sa = (struct sockaddr *)(void *)p; + len = SA_RLEN(sa); + switch (i) { + case RTAX_GATEWAY: + ift->ifma_lladdr = + (struct sockaddr *)(void *)data; + memcpy(data, p, len); + data += len; + break; + + case RTAX_IFP: + ift->ifma_name = + (struct sockaddr *)(void *)data; + memcpy(data, p, len); + data += len; + break; + + case RTAX_IFA: + ift->ifma_addr = + (struct sockaddr *)(void *)data; + memcpy(data, p, len); + data += len; + break; + + default: + data += len; + break; + } + p += len; + } + ift->ifma_next = ift + 1; + ift = ift->ifma_next; + break; + } + } + + free(buf); + + if (ift > ifa) { + ift--; + ift->ifma_next = NULL; + *pif = ifa; + } else { + *pif = NULL; + free(ifa); + } + return (0); +} + +static inline void _intl_freeifmaddrs(struct _intl_ifmaddrs *ifmp) +{ + free(ifmp); +} + +// -------------------------------------------------------------------------- +// -------------------------------------------------------------------------- + +#include +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" +#include "../node/Mutex.hpp" +#include "../node/Dictionary.hpp" +#include "OSUtils.hpp" +#include "OSXEthernetTap.hpp" + +// ff:ff:ff:ff:ff:ff with no ADI +static const ZeroTier::MulticastGroup _blindWildcardMulticastGroup(ZeroTier::MAC(0xff),0); + +static inline bool _setIpv6Stuff(const char *ifname,bool performNUD,bool acceptRouterAdverts) +{ + struct in6_ndireq nd; + struct in6_ifreq ifr; + + int s = socket(AF_INET6,SOCK_DGRAM,0); + if (s <= 0) + return false; + + memset(&nd,0,sizeof(nd)); + strncpy(nd.ifname,ifname,sizeof(nd.ifname)); + + if (ioctl(s,SIOCGIFINFO_IN6,&nd)) { + close(s); + return false; + } + + unsigned long oldFlags = (unsigned long)nd.ndi.flags; + + if (performNUD) + nd.ndi.flags |= ND6_IFF_PERFORMNUD; + else nd.ndi.flags &= ~ND6_IFF_PERFORMNUD; + + if (oldFlags != (unsigned long)nd.ndi.flags) { + if (ioctl(s,SIOCSIFINFO_FLAGS,&nd)) { + close(s); + return false; + } + } + + memset(&ifr,0,sizeof(ifr)); + strncpy(ifr.ifr_name,ifname,sizeof(ifr.ifr_name)); + if (ioctl(s,acceptRouterAdverts ? SIOCAUTOCONF_START : SIOCAUTOCONF_STOP,&ifr)) { + close(s); + return false; + } + + close(s); + return true; +} + +namespace ZeroTier { + +static long globalTapsRunning = 0; +static Mutex globalTapCreateLock; + +OSXEthernetTap::OSXEthernetTap( + const char *homePath, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *friendlyName, + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *data,unsigned int len), + void *arg) : + _handler(handler), + _arg(arg), + _nwid(nwid), + _homePath(homePath), + _mtu(mtu), + _metric(metric), + _fd(0), + _enabled(true) +{ + char devpath[64],ethaddr[64],mtustr[32],metstr[32],nwids[32]; + struct stat stattmp; + + Utils::snprintf(nwids,sizeof(nwids),"%.16llx",nwid); + + if (mtu > 2800) + throw std::runtime_error("max tap MTU is 2800"); + + Mutex::Lock _gl(globalTapCreateLock); + + if (::stat("/dev/zt0",&stattmp)) { + long kextpid = (long)vfork(); + if (kextpid == 0) { + ::chdir(homePath); + OSUtils::redirectUnixOutputs("/dev/null",(const char *)0); + ::execl("/sbin/kextload","/sbin/kextload","-q","-repository",homePath,"tap.kext",(const char *)0); + ::_exit(-1); + } else if (kextpid > 0) { + int exitcode = -1; + ::waitpid(kextpid,&exitcode,0); + } + ::usleep(500); // give tap device driver time to start up and try again + if (::stat("/dev/zt0",&stattmp)) + throw std::runtime_error("/dev/zt# tap devices do not exist and cannot load tap.kext"); + } + + // Try to reopen the last device we had, if we had one and it's still unused. + std::map globalDeviceMap; + FILE *devmapf = fopen((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),"r"); + if (devmapf) { + char buf[256]; + while (fgets(buf,sizeof(buf),devmapf)) { + char *x = (char *)0; + char *y = (char *)0; + char *saveptr = (char *)0; + for(char *f=Utils::stok(buf,"\r\n=",&saveptr);(f);f=Utils::stok((char *)0,"\r\n=",&saveptr)) { + if (!x) x = f; + else if (!y) y = f; + else break; + } + if ((x)&&(y)&&(x[0])&&(y[0])) + globalDeviceMap[x] = y; + } + fclose(devmapf); + } + bool recalledDevice = false; + std::map::const_iterator gdmEntry = globalDeviceMap.find(nwids); + if (gdmEntry != globalDeviceMap.end()) { + std::string devpath("/dev/"); devpath.append(gdmEntry->second); + if (stat(devpath.c_str(),&stattmp) == 0) { + _fd = ::open(devpath.c_str(),O_RDWR); + if (_fd > 0) { + _dev = gdmEntry->second; + recalledDevice = true; + } + } + } + + // Open the first unused tap device if we didn't recall a previous one. + if (!recalledDevice) { + for(int i=0;i<64;++i) { + Utils::snprintf(devpath,sizeof(devpath),"/dev/zt%d",i); + if (stat(devpath,&stattmp)) + throw std::runtime_error("no more TAP devices available"); + _fd = ::open(devpath,O_RDWR); + if (_fd > 0) { + char foo[16]; + Utils::snprintf(foo,sizeof(foo),"zt%d",i); + _dev = foo; + break; + } + } + } + + if (_fd <= 0) + throw std::runtime_error("unable to open TAP device or no more devices available"); + + if (fcntl(_fd,F_SETFL,fcntl(_fd,F_GETFL) & ~O_NONBLOCK) == -1) { + ::close(_fd); + throw std::runtime_error("unable to set flags on file descriptor for TAP device"); + } + + // Configure MAC address and MTU, bring interface up + Utils::snprintf(ethaddr,sizeof(ethaddr),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(int)mac[0],(int)mac[1],(int)mac[2],(int)mac[3],(int)mac[4],(int)mac[5]); + Utils::snprintf(mtustr,sizeof(mtustr),"%u",_mtu); + Utils::snprintf(metstr,sizeof(metstr),"%u",_metric); + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),"lladdr",ethaddr,"mtu",mtustr,"metric",metstr,"up",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + if (exitcode) { + ::close(_fd); + throw std::runtime_error("ifconfig failure setting link-layer address and activating tap interface"); + } + } + + _setIpv6Stuff(_dev.c_str(),true,false); + + // Set close-on-exec so that devices cannot persist if we fork/exec for update + fcntl(_fd,F_SETFD,fcntl(_fd,F_GETFD) | FD_CLOEXEC); + + ::pipe(_shutdownSignalPipe); + + ++globalTapsRunning; + + globalDeviceMap[nwids] = _dev; + devmapf = fopen((_homePath + ZT_PATH_SEPARATOR_S + "devicemap").c_str(),"w"); + if (devmapf) { + gdmEntry = globalDeviceMap.begin(); + while (gdmEntry != globalDeviceMap.end()) { + fprintf(devmapf,"%s=%s\n",gdmEntry->first.c_str(),gdmEntry->second.c_str()); + ++gdmEntry; + } + fclose(devmapf); + } + + _thread = Thread::start(this); +} + +OSXEthernetTap::~OSXEthernetTap() +{ + ::write(_shutdownSignalPipe[1],"\0",1); // causes thread to exit + Thread::join(_thread); + + ::close(_fd); + ::close(_shutdownSignalPipe[0]); + ::close(_shutdownSignalPipe[1]); + + { + Mutex::Lock _gl(globalTapCreateLock); + if (--globalTapsRunning <= 0) { + globalTapsRunning = 0; // sanity check -- should not be possible + + char tmp[16384]; + sprintf(tmp,"%s/%s",_homePath.c_str(),"tap.kext"); + long kextpid = (long)vfork(); + if (kextpid == 0) { + OSUtils::redirectUnixOutputs("/dev/null",(const char *)0); + ::execl("/sbin/kextunload","/sbin/kextunload",tmp,(const char *)0); + ::_exit(-1); + } else if (kextpid > 0) { + int exitcode = -1; + ::waitpid(kextpid,&exitcode,0); + } + } + } +} + +void OSXEthernetTap::setEnabled(bool en) +{ + _enabled = en; + // TODO: interface status change +} + +bool OSXEthernetTap::enabled() const +{ + return _enabled; +} + +bool OSXEthernetTap::addIp(const InetAddress &ip) +{ + if (!ip) + return false; + + long cpid = (long)vfork(); + if (cpid == 0) { + ::execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),(ip.ss_family == AF_INET6) ? "inet6" : "inet",ip.toString().c_str(),"alias",(const char *)0); + ::_exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + ::waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } // else return false... + + return false; +} + +bool OSXEthernetTap::removeIp(const InetAddress &ip) +{ + if (!ip) + return true; + std::vector allIps(ips()); + for(std::vector::iterator i(allIps.begin());i!=allIps.end();++i) { + if (*i == ip) { + long cpid = (long)vfork(); + if (cpid == 0) { + execl("/sbin/ifconfig","/sbin/ifconfig",_dev.c_str(),(ip.ss_family == AF_INET6) ? "inet6" : "inet",ip.toIpString().c_str(),"-alias",(const char *)0); + _exit(-1); + } else if (cpid > 0) { + int exitcode = -1; + waitpid(cpid,&exitcode,0); + return (exitcode == 0); + } + } + } + return false; +} + +std::vector OSXEthernetTap::ips() const +{ + struct ifaddrs *ifa = (struct ifaddrs *)0; + if (getifaddrs(&ifa)) + return std::vector(); + + std::vector r; + + struct ifaddrs *p = ifa; + while (p) { + if ((!strcmp(p->ifa_name,_dev.c_str()))&&(p->ifa_addr)&&(p->ifa_netmask)&&(p->ifa_addr->sa_family == p->ifa_netmask->sa_family)) { + switch(p->ifa_addr->sa_family) { + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *)p->ifa_addr; + struct sockaddr_in *nm = (struct sockaddr_in *)p->ifa_netmask; + r.push_back(InetAddress(&(sin->sin_addr.s_addr),4,Utils::countBits((uint32_t)nm->sin_addr.s_addr))); + } break; + case AF_INET6: { + struct sockaddr_in6 *sin = (struct sockaddr_in6 *)p->ifa_addr; + struct sockaddr_in6 *nm = (struct sockaddr_in6 *)p->ifa_netmask; + uint32_t b[4]; + memcpy(b,nm->sin6_addr.s6_addr,sizeof(b)); + r.push_back(InetAddress(sin->sin6_addr.s6_addr,16,Utils::countBits(b[0]) + Utils::countBits(b[1]) + Utils::countBits(b[2]) + Utils::countBits(b[3]))); + } break; + } + } + p = p->ifa_next; + } + + if (ifa) + freeifaddrs(ifa); + + std::sort(r.begin(),r.end()); + r.erase(std::unique(r.begin(),r.end()),r.end()); + + return r; +} + +void OSXEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + char putBuf[4096]; + if ((_fd > 0)&&(len <= _mtu)&&(_enabled)) { + to.copyTo(putBuf,6); + from.copyTo(putBuf + 6,6); + *((uint16_t *)(putBuf + 12)) = htons((uint16_t)etherType); + memcpy(putBuf + 14,data,len); + len += 14; + ::write(_fd,putBuf,len); + } +} + +std::string OSXEthernetTap::deviceName() const +{ + return _dev; +} + +void OSXEthernetTap::setFriendlyName(const char *friendlyName) +{ +} + +void OSXEthernetTap::scanMulticastGroups(std::vector &added,std::vector &removed) +{ + std::vector newGroups; + + struct _intl_ifmaddrs *ifmap = (struct _intl_ifmaddrs *)0; + if (!_intl_getifmaddrs(&ifmap)) { + struct _intl_ifmaddrs *p = ifmap; + while (p) { + if (p->ifma_addr->sa_family == AF_LINK) { + struct sockaddr_dl *in = (struct sockaddr_dl *)p->ifma_name; + struct sockaddr_dl *la = (struct sockaddr_dl *)p->ifma_addr; + if ((la->sdl_alen == 6)&&(in->sdl_nlen <= _dev.length())&&(!memcmp(_dev.data(),in->sdl_data,in->sdl_nlen))) + newGroups.push_back(MulticastGroup(MAC(la->sdl_data + la->sdl_nlen,6),0)); + } + p = p->ifma_next; + } + _intl_freeifmaddrs(ifmap); + } + + std::vector allIps(ips()); + for(std::vector::iterator ip(allIps.begin());ip!=allIps.end();++ip) + newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip)); + + std::sort(newGroups.begin(),newGroups.end()); + std::unique(newGroups.begin(),newGroups.end()); + + for(std::vector::iterator m(newGroups.begin());m!=newGroups.end();++m) { + if (!std::binary_search(_multicastGroups.begin(),_multicastGroups.end(),*m)) + added.push_back(*m); + } + for(std::vector::iterator m(_multicastGroups.begin());m!=_multicastGroups.end();++m) { + if (!std::binary_search(newGroups.begin(),newGroups.end(),*m)) + removed.push_back(*m); + } + + _multicastGroups.swap(newGroups); +} + +void OSXEthernetTap::threadMain() + throw() +{ + fd_set readfds,nullfds; + MAC to,from; + int n,nfds,r; + char getBuf[8194]; + + Thread::sleep(500); + + FD_ZERO(&readfds); + FD_ZERO(&nullfds); + nfds = (int)std::max(_shutdownSignalPipe[0],_fd) + 1; + + r = 0; + for(;;) { + FD_SET(_shutdownSignalPipe[0],&readfds); + FD_SET(_fd,&readfds); + select(nfds,&readfds,&nullfds,&nullfds,(struct timeval *)0); + + if (FD_ISSET(_shutdownSignalPipe[0],&readfds)) // writes to shutdown pipe terminate thread + break; + + if (FD_ISSET(_fd,&readfds)) { + n = (int)::read(_fd,getBuf + r,sizeof(getBuf) - r); + if (n < 0) { + if ((errno != EINTR)&&(errno != ETIMEDOUT)) + break; + } else { + // Some tap drivers like to send the ethernet frame and the + // payload in two chunks, so handle that by accumulating + // data until we have at least a frame. + r += n; + if (r > 14) { + if (r > ((int)_mtu + 14)) // sanity check for weird TAP behavior on some platforms + r = _mtu + 14; + + if (_enabled) { + to.setTo(getBuf,6); + from.setTo(getBuf + 6,6); + unsigned int etherType = ntohs(((const uint16_t *)getBuf)[6]); + // TODO: VLAN support + _handler(_arg,(void *)0,_nwid,from,to,etherType,0,(const void *)(getBuf + 14),r - 14); + } + + r = 0; + } + } + } + } +} + +} // namespace ZeroTier diff --git a/osdep/OSXEthernetTap.hpp b/osdep/OSXEthernetTap.hpp new file mode 100644 index 0000000..5a96c21 --- /dev/null +++ b/osdep/OSXEthernetTap.hpp @@ -0,0 +1,86 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_OSXETHERNETTAP_HPP +#define ZT_OSXETHERNETTAP_HPP + +#include +#include + +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/MAC.hpp" +#include "../node/InetAddress.hpp" +#include "../node/MulticastGroup.hpp" + +#include "Thread.hpp" + +namespace ZeroTier { + +/** + * OSX Ethernet tap using ZeroTier kernel extension zt# devices + */ +class OSXEthernetTap +{ +public: + OSXEthernetTap( + const char *homePath, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *friendlyName, + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void *arg); + + ~OSXEthernetTap(); + + void setEnabled(bool en); + bool enabled() const; + bool addIp(const InetAddress &ip); + bool removeIp(const InetAddress &ip); + std::vector ips() const; + void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + std::string deviceName() const; + void setFriendlyName(const char *friendlyName); + void scanMulticastGroups(std::vector &added,std::vector &removed); + + void threadMain() + throw(); + +private: + void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); + void *_arg; + uint64_t _nwid; + Thread _thread; + std::string _homePath; + std::string _dev; + std::vector _multicastGroups; + unsigned int _mtu; + unsigned int _metric; + int _fd; + int _shutdownSignalPipe[2]; + volatile bool _enabled; +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/Phy.hpp b/osdep/Phy.hpp new file mode 100644 index 0000000..eab8a31 --- /dev/null +++ b/osdep/Phy.hpp @@ -0,0 +1,1115 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_PHY_HPP +#define ZT_PHY_HPP + +#include +#include +#include + +#include +#include + +#if defined(_WIN32) || defined(_WIN64) + +#include +#include +#include + +#define ZT_PHY_SOCKFD_TYPE SOCKET +#define ZT_PHY_SOCKFD_NULL (INVALID_SOCKET) +#define ZT_PHY_SOCKFD_VALID(s) ((s) != INVALID_SOCKET) +#define ZT_PHY_CLOSE_SOCKET(s) ::closesocket(s) +#define ZT_PHY_MAX_SOCKETS (FD_SETSIZE) +#define ZT_PHY_MAX_INTERCEPTS ZT_PHY_MAX_SOCKETS +#define ZT_PHY_SOCKADDR_STORAGE_TYPE struct sockaddr_storage + +#else // not Windows + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) +#ifndef IPV6_DONTFRAG +#define IPV6_DONTFRAG 62 +#endif +#endif + +#define ZT_PHY_SOCKFD_TYPE int +#define ZT_PHY_SOCKFD_NULL (-1) +#define ZT_PHY_SOCKFD_VALID(s) ((s) > -1) +#define ZT_PHY_CLOSE_SOCKET(s) ::close(s) +#define ZT_PHY_MAX_SOCKETS (FD_SETSIZE) +#define ZT_PHY_MAX_INTERCEPTS ZT_PHY_MAX_SOCKETS +#define ZT_PHY_SOCKADDR_STORAGE_TYPE struct sockaddr_storage + +#endif // Windows or not + +namespace ZeroTier { + +/** + * Opaque socket type + */ +typedef void PhySocket; + +/** + * Simple templated non-blocking sockets implementation + * + * Yes there is boost::asio and libuv, but I like small binaries and I hate + * build dependencies. Both drag in a whole bunch of pasta with them. + * + * This class is templated on a pointer to a handler class which must + * implement the following functions: + * + * For all platforms: + * + * phyOnDatagram(PhySocket *sock,void **uptr,const struct sockaddr *localAddr,const struct sockaddr *from,void *data,unsigned long len) + * phyOnTcpConnect(PhySocket *sock,void **uptr,bool success) + * phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from) + * phyOnTcpClose(PhySocket *sock,void **uptr) + * phyOnTcpData(PhySocket *sock,void **uptr,void *data,unsigned long len) + * phyOnTcpWritable(PhySocket *sock,void **uptr) + * phyOnFileDescriptorActivity(PhySocket *sock,void **uptr,bool readable,bool writable) + * + * On Linux/OSX/Unix only (not required/used on Windows or elsewhere): + * + * phyOnUnixAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN) + * phyOnUnixClose(PhySocket *sock,void **uptr) + * phyOnUnixData(PhySocket *sock,void **uptr,void *data,unsigned long len) + * phyOnUnixWritable(PhySocket *sock,void **uptr) + * + * These templates typically refer to function objects. Templates are used to + * avoid the call overhead of indirection, which is surprisingly high for high + * bandwidth applications pushing a lot of packets. + * + * The 'sock' pointer above is an opaque pointer to a socket. Each socket + * has a 'uptr' user-settable/modifiable pointer associated with it, which + * can be set on bind/connect calls and is passed as a void ** to permit + * resetting at any time. The ACCEPT handler takes two sets of sock and + * uptr: sockL and uptrL for the listen socket, and sockN and uptrN for + * the new TCP connection socket that has just been created. + * + * Handlers are always called. On outgoing TCP connection, CONNECT is always + * called on either success or failure followed by DATA and/or WRITABLE as + * indicated. On socket close, handlers are called unless close() is told + * explicitly not to call handlers. It is safe to close a socket within a + * handler, and in that case close() can be told not to call handlers to + * prevent recursion. + * + * This isn't thread-safe with the exception of whack(), which is safe to + * call from another thread to abort poll(). + */ +template +class Phy +{ +private: + HANDLER_PTR_TYPE _handler; + + enum PhySocketType + { + ZT_PHY_SOCKET_CLOSED = 0x00, // socket is closed, will be removed on next poll() + ZT_PHY_SOCKET_TCP_OUT_PENDING = 0x01, + ZT_PHY_SOCKET_TCP_OUT_CONNECTED = 0x02, + ZT_PHY_SOCKET_TCP_IN = 0x03, + ZT_PHY_SOCKET_TCP_LISTEN = 0x04, + ZT_PHY_SOCKET_UDP = 0x05, + ZT_PHY_SOCKET_FD = 0x06, + ZT_PHY_SOCKET_UNIX_IN = 0x07, + ZT_PHY_SOCKET_UNIX_LISTEN = 0x08 + }; + + struct PhySocketImpl + { + PhySocketType type; + ZT_PHY_SOCKFD_TYPE sock; + void *uptr; // user-settable pointer + ZT_PHY_SOCKADDR_STORAGE_TYPE saddr; // remote for TCP_OUT and TCP_IN, local for TCP_LISTEN, RAW, and UDP + }; + + std::list _socks; + fd_set _readfds; + fd_set _writefds; +#if defined(_WIN32) || defined(_WIN64) + fd_set _exceptfds; +#endif + long _nfds; + + ZT_PHY_SOCKFD_TYPE _whackReceiveSocket; + ZT_PHY_SOCKFD_TYPE _whackSendSocket; + + bool _noDelay; + bool _noCheck; + +public: + /** + * @param handler Pointer of type HANDLER_PTR_TYPE to handler + * @param noDelay If true, disable TCP NAGLE algorithm on TCP sockets + * @param noCheck If true, attempt to set UDP SO_NO_CHECK option to disable sending checksums + */ + Phy(HANDLER_PTR_TYPE handler,bool noDelay,bool noCheck) : + _handler(handler) + { + FD_ZERO(&_readfds); + FD_ZERO(&_writefds); + +#if defined(_WIN32) || defined(_WIN64) + FD_ZERO(&_exceptfds); + + SOCKET pipes[2]; + { // hack copied from StackOverflow, behaves a bit like pipe() on *nix systems + struct sockaddr_in inaddr; + struct sockaddr addr; + SOCKET lst=::socket(AF_INET, SOCK_STREAM,IPPROTO_TCP); + if (lst == INVALID_SOCKET) + throw std::runtime_error("unable to create pipes for select() abort"); + memset(&inaddr, 0, sizeof(inaddr)); + memset(&addr, 0, sizeof(addr)); + inaddr.sin_family = AF_INET; + inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + inaddr.sin_port = 0; + int yes=1; + setsockopt(lst,SOL_SOCKET,SO_REUSEADDR,(char*)&yes,sizeof(yes)); + bind(lst,(struct sockaddr *)&inaddr,sizeof(inaddr)); + listen(lst,1); + int len=sizeof(inaddr); + getsockname(lst, &addr,&len); + pipes[0]=::socket(AF_INET, SOCK_STREAM,0); + if (pipes[0] == INVALID_SOCKET) + throw std::runtime_error("unable to create pipes for select() abort"); + connect(pipes[0],&addr,len); + pipes[1]=accept(lst,0,0); + closesocket(lst); + } +#else // not Windows + int pipes[2]; + if (::pipe(pipes)) + throw std::runtime_error("unable to create pipes for select() abort"); +#endif // Windows or not + + _nfds = (pipes[0] > pipes[1]) ? (long)pipes[0] : (long)pipes[1]; + _whackReceiveSocket = pipes[0]; + _whackSendSocket = pipes[1]; + _noDelay = noDelay; + _noCheck = noCheck; + } + + ~Phy() + { + for(typename std::list::const_iterator s(_socks.begin());s!=_socks.end();++s) { + if (s->type != ZT_PHY_SOCKET_CLOSED) + this->close((PhySocket *)&(*s),true); + } + ZT_PHY_CLOSE_SOCKET(_whackReceiveSocket); + ZT_PHY_CLOSE_SOCKET(_whackSendSocket); + } + + /** + * @param s Socket object + * @return Underlying OS-type (usually int or long) file descriptor associated with object + */ + static inline ZT_PHY_SOCKFD_TYPE getDescriptor(PhySocket *s) throw() { return reinterpret_cast(s)->sock; } + + /** + * @param s Socket object + * @return Pointer to user object + */ + static inline void** getuptr(PhySocket *s) throw() { return &(reinterpret_cast(s)->uptr); } + + /** + * Cause poll() to stop waiting immediately + * + * This can be used to reset the polling loop after changes that require + * attention, or to shut down a background thread that is waiting, etc. + */ + inline void whack() + { +#if defined(_WIN32) || defined(_WIN64) + ::send(_whackSendSocket,(const char *)this,1,0); +#else + (void)(::write(_whackSendSocket,(PhySocket *)this,1)); +#endif + } + + /** + * @return Number of open sockets + */ + inline unsigned long count() const throw() { return _socks.size(); } + + /** + * @return Maximum number of sockets allowed + */ + inline unsigned long maxCount() const throw() { return ZT_PHY_MAX_SOCKETS; } + + /** + * Wrap a raw file descriptor in a PhySocket structure + * + * This can be used to select/poll on a raw file descriptor as part of this + * class's I/O loop. By default the fd is set for read notification but + * this can be controlled with setNotifyReadable(). When any detected + * condition is present, the phyOnFileDescriptorActivity() callback is + * called with one or both of its arguments 'true'. + * + * The Phy<>::close() method *must* be called when you're done with this + * file descriptor to remove it from the select/poll set, but unlike other + * types of sockets Phy<> does not actually close the underlying fd or + * otherwise manage its life cycle. There is also no close notification + * callback for this fd, since Phy<> doesn't actually perform reading or + * writing or detect error conditions. This is only useful for adding a + * file descriptor to Phy<> to select/poll on it. + * + * @param fd Raw file descriptor + * @param uptr User pointer to supply to callbacks + * @return PhySocket wrapping fd or NULL on failure (out of memory or too many sockets) + */ + inline PhySocket *wrapSocket(ZT_PHY_SOCKFD_TYPE fd,void *uptr = (void *)0) + { + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) + return (PhySocket *)0; + try { + _socks.push_back(PhySocketImpl()); + } catch ( ... ) { + return (PhySocket *)0; + } + PhySocketImpl &sws = _socks.back(); + if ((long)fd > _nfds) + _nfds = (long)fd; + FD_SET(fd,&_readfds); + sws.type = ZT_PHY_SOCKET_UNIX_IN; /* TODO: Type was changed to allow for CBs with new RPC model */ + sws.sock = fd; + sws.uptr = uptr; + memset(&(sws.saddr),0,sizeof(struct sockaddr_storage)); + // no sockaddr for this socket type, leave saddr null + return (PhySocket *)&sws; + } + + /** + * Bind a UDP socket + * + * @param localAddress Local endpoint address and port + * @param uptr Initial value of user pointer associated with this socket (default: NULL) + * @param bufferSize Desired socket receive/send buffer size -- will set as close to this as possible (default: 0, leave alone) + * @return Socket or NULL on failure to bind + */ + inline PhySocket *udpBind(const struct sockaddr *localAddress,void *uptr = (void *)0,int bufferSize = 0) + { + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) + return (PhySocket *)0; + + ZT_PHY_SOCKFD_TYPE s = ::socket(localAddress->sa_family,SOCK_DGRAM,0); + if (!ZT_PHY_SOCKFD_VALID(s)) + return (PhySocket *)0; + + if (bufferSize > 0) { + int bs = bufferSize; + while (bs >= 65536) { + int tmpbs = bs; + if (setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char *)&tmpbs,sizeof(tmpbs)) == 0) + break; + bs -= 16384; + } + bs = bufferSize; + while (bs >= 65536) { + int tmpbs = bs; + if (setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char *)&tmpbs,sizeof(tmpbs)) == 0) + break; + bs -= 16384; + } + } + +#if defined(_WIN32) || defined(_WIN64) + { + BOOL f; + if (localAddress->sa_family == AF_INET6) { + f = TRUE; setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(const char *)&f,sizeof(f)); + f = FALSE; setsockopt(s,IPPROTO_IPV6,IPV6_DONTFRAG,(const char *)&f,sizeof(f)); + } + f = FALSE; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(const char *)&f,sizeof(f)); + f = TRUE; setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char *)&f,sizeof(f)); + } +#else // not Windows + { + int f; + if (localAddress->sa_family == AF_INET6) { + f = 1; setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(void *)&f,sizeof(f)); +#ifdef IPV6_MTU_DISCOVER + f = 0; setsockopt(s,IPPROTO_IPV6,IPV6_MTU_DISCOVER,&f,sizeof(f)); +#endif +#ifdef IPV6_DONTFRAG + f = 0; setsockopt(s,IPPROTO_IPV6,IPV6_DONTFRAG,&f,sizeof(f)); +#endif + } + f = 0; setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(void *)&f,sizeof(f)); + f = 1; setsockopt(s,SOL_SOCKET,SO_BROADCAST,(void *)&f,sizeof(f)); +#ifdef IP_DONTFRAG + f = 0; setsockopt(s,IPPROTO_IP,IP_DONTFRAG,&f,sizeof(f)); +#endif +#ifdef IP_MTU_DISCOVER + f = 0; setsockopt(s,IPPROTO_IP,IP_MTU_DISCOVER,&f,sizeof(f)); +#endif +#ifdef SO_NO_CHECK + // For now at least we only set SO_NO_CHECK on IPv4 sockets since some + // IPv6 stacks incorrectly discard zero checksum packets. May remove + // this restriction later once broken stuff dies more. + if ((localAddress->sa_family == AF_INET)&&(_noCheck)) { + f = 1; setsockopt(s,SOL_SOCKET,SO_NO_CHECK,(void *)&f,sizeof(f)); + } +#endif + } +#endif // Windows or not + + if (::bind(s,localAddress,(localAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + +#if defined(_WIN32) || defined(_WIN64) + { u_long iMode=1; ioctlsocket(s,FIONBIO,&iMode); } +#else + fcntl(s,F_SETFL,O_NONBLOCK); +#endif + + try { + _socks.push_back(PhySocketImpl()); + } catch ( ... ) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + PhySocketImpl &sws = _socks.back(); + + if ((long)s > _nfds) + _nfds = (long)s; + FD_SET(s,&_readfds); + sws.type = ZT_PHY_SOCKET_UDP; + sws.sock = s; + sws.uptr = uptr; + memset(&(sws.saddr),0,sizeof(struct sockaddr_storage)); + memcpy(&(sws.saddr),localAddress,(localAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + + return (PhySocket *)&sws; + } + + /** + * Set the IP TTL for the next outgoing packet (for IPv4 UDP sockets only) + * + * @param ttl New TTL (0 or >255 will set it to 255) + * @return True on success + */ + inline bool setIp4UdpTtl(PhySocket *sock,unsigned int ttl) + { + PhySocketImpl &sws = *(reinterpret_cast(sock)); +#if defined(_WIN32) || defined(_WIN64) + DWORD tmp = ((ttl == 0)||(ttl > 255)) ? 255 : (DWORD)ttl; + return (::setsockopt(sws.sock,IPPROTO_IP,IP_TTL,(const char *)&tmp,sizeof(tmp)) == 0); +#else + int tmp = ((ttl == 0)||(ttl > 255)) ? 255 : (int)ttl; + return (::setsockopt(sws.sock,IPPROTO_IP,IP_TTL,(void *)&tmp,sizeof(tmp)) == 0); +#endif + } + + /** + * Send a UDP packet + * + * @param sock UDP socket + * @param remoteAddress Destination address (must be correct type for socket) + * @param data Data to send + * @param len Length of packet + * @return True if packet appears to have been sent successfully + */ + inline bool udpSend(PhySocket *sock,const struct sockaddr *remoteAddress,const void *data,unsigned long len) + { + PhySocketImpl &sws = *(reinterpret_cast(sock)); +#if defined(_WIN32) || defined(_WIN64) + return ((long)::sendto(sws.sock,reinterpret_cast(data),len,0,remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == (long)len); +#else + return ((long)::sendto(sws.sock,data,len,0,remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == (long)len); +#endif + } + +#ifdef __UNIX_LIKE__ + /** + * Listen for connections on a Unix domain socket + * + * @param path Path to Unix domain socket + * @param uptr Arbitrary pointer to associate + * @return PhySocket or NULL if cannot bind + */ + inline PhySocket *unixListen(const char *path,void *uptr = (void *)0) + { + struct sockaddr_un sun; + + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) + return (PhySocket *)0; + + memset(&sun,0,sizeof(sun)); + sun.sun_family = AF_UNIX; + if (strlen(path) >= sizeof(sun.sun_path)) + return (PhySocket *)0; + strcpy(sun.sun_path,path); + + ZT_PHY_SOCKFD_TYPE s = ::socket(PF_UNIX,SOCK_STREAM,0); + if (!ZT_PHY_SOCKFD_VALID(s)) + return (PhySocket *)0; + + ::fcntl(s,F_SETFL,O_NONBLOCK); + + ::unlink(path); + if (::bind(s,(struct sockaddr *)&sun,sizeof(struct sockaddr_un)) != 0) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + if (::listen(s,128) != 0) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + + try { + _socks.push_back(PhySocketImpl()); + } catch ( ... ) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + PhySocketImpl &sws = _socks.back(); + + if ((long)s > _nfds) + _nfds = (long)s; + FD_SET(s,&_readfds); + sws.type = ZT_PHY_SOCKET_UNIX_LISTEN; + sws.sock = s; + sws.uptr = uptr; + memset(&(sws.saddr),0,sizeof(struct sockaddr_storage)); + memcpy(&(sws.saddr),&sun,sizeof(struct sockaddr_un)); + + return (PhySocket *)&sws; + } +#endif // __UNIX_LIKE__ + + /** + * Bind a local listen socket to listen for new TCP connections + * + * @param localAddress Local address and port + * @param uptr Initial value of uptr for new socket (default: NULL) + * @return Socket or NULL on failure to bind + */ + inline PhySocket *tcpListen(const struct sockaddr *localAddress,void *uptr = (void *)0) + { + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) + return (PhySocket *)0; + + ZT_PHY_SOCKFD_TYPE s = ::socket(localAddress->sa_family,SOCK_STREAM,0); + if (!ZT_PHY_SOCKFD_VALID(s)) + return (PhySocket *)0; + +#if defined(_WIN32) || defined(_WIN64) + { + BOOL f; + f = TRUE; ::setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(const char *)&f,sizeof(f)); + f = TRUE; ::setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(const char *)&f,sizeof(f)); + f = (_noDelay ? TRUE : FALSE); setsockopt(s,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); + u_long iMode=1; + ioctlsocket(s,FIONBIO,&iMode); + } +#else + { + int f; + f = 1; ::setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(void *)&f,sizeof(f)); + f = 1; ::setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(void *)&f,sizeof(f)); + f = (_noDelay ? 1 : 0); setsockopt(s,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); + fcntl(s,F_SETFL,O_NONBLOCK); + } +#endif + + if (::bind(s,localAddress,(localAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + + if (::listen(s,1024)) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + + try { + _socks.push_back(PhySocketImpl()); + } catch ( ... ) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + PhySocketImpl &sws = _socks.back(); + + if ((long)s > _nfds) + _nfds = (long)s; + FD_SET(s,&_readfds); + sws.type = ZT_PHY_SOCKET_TCP_LISTEN; + sws.sock = s; + sws.uptr = uptr; + memset(&(sws.saddr),0,sizeof(struct sockaddr_storage)); + memcpy(&(sws.saddr),localAddress,(localAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + + return (PhySocket *)&sws; + } + + /** + * Start a non-blocking connect; CONNECT handler is called on success or failure + * + * A return value of NULL indicates a synchronous failure such as a + * failure to open a socket. The TCP connection handler is not called + * in this case. + * + * It is possible on some platforms for an "instant connect" to occur, + * such as when connecting to a loopback address. In this case, the + * 'connected' result parameter will be set to 'true' and if the + * 'callConnectHandler' flag is true (the default) the TCP connect + * handler will be called before the function returns. + * + * These semantics can be a bit confusing, but they're less so than + * the underlying semantics of asynchronous TCP connect. + * + * @param remoteAddress Remote address + * @param connected Result parameter: set to whether an "instant connect" has occurred (true if yes) + * @param uptr Initial value of uptr for new socket (default: NULL) + * @param callConnectHandler If true, call TCP connect handler even if result is known before function exit (default: true) + * @return New socket or NULL on failure + */ + inline PhySocket *tcpConnect(const struct sockaddr *remoteAddress,bool &connected,void *uptr = (void *)0,bool callConnectHandler = true) + { + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) + return (PhySocket *)0; + + ZT_PHY_SOCKFD_TYPE s = ::socket(remoteAddress->sa_family,SOCK_STREAM,0); + if (!ZT_PHY_SOCKFD_VALID(s)) { + connected = false; + return (PhySocket *)0; + } + +#if defined(_WIN32) || defined(_WIN64) + { + BOOL f; + if (remoteAddress->sa_family == AF_INET6) { f = TRUE; ::setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(const char *)&f,sizeof(f)); } + f = TRUE; ::setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(const char *)&f,sizeof(f)); + f = (_noDelay ? TRUE : FALSE); setsockopt(s,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); + u_long iMode=1; + ioctlsocket(s,FIONBIO,&iMode); + } +#else + { + int f; + if (remoteAddress->sa_family == AF_INET6) { f = 1; ::setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,(void *)&f,sizeof(f)); } + f = 1; ::setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(void *)&f,sizeof(f)); + f = (_noDelay ? 1 : 0); setsockopt(s,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); + fcntl(s,F_SETFL,O_NONBLOCK); + } +#endif + + connected = true; + if (::connect(s,remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))) { + connected = false; +#if defined(_WIN32) || defined(_WIN64) + if (WSAGetLastError() != WSAEWOULDBLOCK) { +#else + if (errno != EINPROGRESS) { +#endif + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } // else connection is proceeding asynchronously... + } + + try { + _socks.push_back(PhySocketImpl()); + } catch ( ... ) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + PhySocketImpl &sws = _socks.back(); + + if ((long)s > _nfds) + _nfds = (long)s; + if (connected) { + FD_SET(s,&_readfds); + sws.type = ZT_PHY_SOCKET_TCP_OUT_CONNECTED; + } else { + FD_SET(s,&_writefds); +#if defined(_WIN32) || defined(_WIN64) + FD_SET(s,&_exceptfds); +#endif + sws.type = ZT_PHY_SOCKET_TCP_OUT_PENDING; + } + sws.sock = s; + sws.uptr = uptr; + memset(&(sws.saddr),0,sizeof(struct sockaddr_storage)); + memcpy(&(sws.saddr),remoteAddress,(remoteAddress->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + + if ((callConnectHandler)&&(connected)) { + try { + _handler->phyOnTcpConnect((PhySocket *)&sws,&(sws.uptr),true); + } catch ( ... ) {} + } + + return (PhySocket *)&sws; + } + + /** + * Try to set buffer sizes as close to the given value as possible + * + * This will try the specified value and then lower values in 16K increments + * until one works. + * + * @param sock Socket + * @param bufferSize Desired buffer sizes + */ + inline void setBufferSizes(const PhySocket *sock,int bufferSize) + { + PhySocketImpl &sws = *(reinterpret_cast(sock)); + if (bufferSize > 0) { + int bs = bufferSize; + while (bs >= 65536) { + int tmpbs = bs; + if (::setsockopt(sws.sock,SOL_SOCKET,SO_RCVBUF,(const char *)&tmpbs,sizeof(tmpbs)) == 0) + break; + bs -= 16384; + } + bs = bufferSize; + while (bs >= 65536) { + int tmpbs = bs; + if (::setsockopt(sws.sock,SOL_SOCKET,SO_SNDBUF,(const char *)&tmpbs,sizeof(tmpbs)) == 0) + break; + bs -= 16384; + } + } + } + + /** + * Attempt to send data to a stream socket (non-blocking) + * + * If -1 is returned, the socket should no longer be used as it is now + * destroyed. If callCloseHandler is true, the close handler will be + * called before the function returns. + * + * This can be used with TCP, Unix, or socket pair sockets. + * + * @param sock An open stream socket (other socket types will fail) + * @param data Data to send + * @param len Length of data + * @param callCloseHandler If true, call close handler on socket closing failure condition (default: true) + * @return Number of bytes actually sent or -1 on fatal error (socket closure) + */ + inline long streamSend(PhySocket *sock,const void *data,unsigned long len,bool callCloseHandler = true) + { + PhySocketImpl &sws = *(reinterpret_cast(sock)); +#if defined(_WIN32) || defined(_WIN64) + long n = (long)::send(sws.sock,reinterpret_cast(data),len,0); + if (n == SOCKET_ERROR) { + switch(WSAGetLastError()) { + case WSAEINTR: + case WSAEWOULDBLOCK: + return 0; + default: + this->close(sock,callCloseHandler); + return -1; + } + } +#else // not Windows + long n = (long)::send(sws.sock,data,len,0); + if (n < 0) { + switch(errno) { +#ifdef EAGAIN + case EAGAIN: +#endif +#if defined(EWOULDBLOCK) && ( !defined(EAGAIN) || (EWOULDBLOCK != EAGAIN) ) + case EWOULDBLOCK: +#endif +#ifdef EINTR + case EINTR: +#endif + return 0; + default: + this->close(sock,callCloseHandler); + return -1; + } + } +#endif // Windows or not + return n; + } + +#ifdef __UNIX_LIKE__ + /** + * Attempt to send data to a Unix domain socket connection (non-blocking) + * + * If -1 is returned, the socket should no longer be used as it is now + * destroyed. If callCloseHandler is true, the close handler will be + * called before the function returns. + * + * @param sock An open Unix socket (other socket types will fail) + * @param data Data to send + * @param len Length of data + * @param callCloseHandler If true, call close handler on socket closing failure condition (default: true) + * @return Number of bytes actually sent or -1 on fatal error (socket closure) + */ + inline long unixSend(PhySocket *sock,const void *data,unsigned long len,bool callCloseHandler = true) + { + PhySocketImpl &sws = *(reinterpret_cast(sock)); + long n = (long)::write(sws.sock,data,len); + if (n < 0) { + switch(errno) { +#ifdef EAGAIN + case EAGAIN: +#endif +#if defined(EWOULDBLOCK) && ( !defined(EAGAIN) || (EWOULDBLOCK != EAGAIN) ) + case EWOULDBLOCK: +#endif +#ifdef EINTR + case EINTR: +#endif + return 0; + default: + this->close(sock,callCloseHandler); + return -1; + } + } + return n; + } +#endif // __UNIX_LIKE__ + + /** + * For streams, sets whether we want to be notified that the socket is writable + * + * This can be used with TCP, Unix, or socket pair sockets. + * + * Call whack() if this is being done from another thread and you want + * it to take effect immediately. Otherwise it is only guaranteed to + * take effect on the next poll(). + * + * @param sock Stream connection socket + * @param notifyWritable Want writable notifications? + */ + inline const void setNotifyWritable(PhySocket *sock,bool notifyWritable) + { + PhySocketImpl &sws = *(reinterpret_cast(sock)); + if (notifyWritable) { + FD_SET(sws.sock,&_writefds); + } else { + FD_CLR(sws.sock,&_writefds); + } + } + + /** + * Set whether we want to be notified that a socket is readable + * + * This is primarily for raw sockets added with wrapSocket(). It could be + * used with others, but doing so would essentially lock them and prevent + * data from being read from them until this is set to 'true' again. + * + * @param sock Socket to modify + * @param notifyReadable True if socket should be monitored for readability + */ + inline const void setNotifyReadable(PhySocket *sock,bool notifyReadable) + { + PhySocketImpl &sws = *(reinterpret_cast(sock)); + if (notifyReadable) { + FD_SET(sws.sock,&_readfds); + } else { + FD_CLR(sws.sock,&_readfds); + } + } + + /** + * Wait for activity and handle one or more events + * + * Note that this is not guaranteed to wait up to 'timeout' even + * if nothing happens, as whack() or other events such as signals + * may cause premature termination. + * + * @param timeout Timeout in milliseconds or 0 for none (forever) + */ + inline void poll(unsigned long timeout) + { + char buf[131072]; + struct sockaddr_storage ss; + struct timeval tv; + fd_set rfds,wfds,efds; + + memcpy(&rfds,&_readfds,sizeof(rfds)); + memcpy(&wfds,&_writefds,sizeof(wfds)); +#if defined(_WIN32) || defined(_WIN64) + memcpy(&efds,&_exceptfds,sizeof(efds)); +#else + FD_ZERO(&efds); +#endif + + tv.tv_sec = (long)(timeout / 1000); + tv.tv_usec = (long)((timeout % 1000) * 1000); + if (::select((int)_nfds + 1,&rfds,&wfds,&efds,(timeout > 0) ? &tv : (struct timeval *)0) <= 0) + return; + + if (FD_ISSET(_whackReceiveSocket,&rfds)) { + char tmp[16]; +#if defined(_WIN32) || defined(_WIN64) + ::recv(_whackReceiveSocket,tmp,16,0); +#else + ::read(_whackReceiveSocket,tmp,16); +#endif + } + + for(typename std::list::iterator s(_socks.begin());s!=_socks.end();) { + switch (s->type) { + + case ZT_PHY_SOCKET_TCP_OUT_PENDING: +#if defined(_WIN32) || defined(_WIN64) + if (FD_ISSET(s->sock,&efds)) { + this->close((PhySocket *)&(*s),true); + } else // ... if +#endif + if (FD_ISSET(s->sock,&wfds)) { + socklen_t slen = sizeof(ss); + if (::getpeername(s->sock,(struct sockaddr *)&ss,&slen) != 0) { + this->close((PhySocket *)&(*s),true); + } else { + s->type = ZT_PHY_SOCKET_TCP_OUT_CONNECTED; + FD_SET(s->sock,&_readfds); + FD_CLR(s->sock,&_writefds); +#if defined(_WIN32) || defined(_WIN64) + FD_CLR(s->sock,&_exceptfds); +#endif + try { + _handler->phyOnTcpConnect((PhySocket *)&(*s),&(s->uptr),true); + } catch ( ... ) {} + } + } + break; + + case ZT_PHY_SOCKET_TCP_OUT_CONNECTED: + case ZT_PHY_SOCKET_TCP_IN: { + ZT_PHY_SOCKFD_TYPE sock = s->sock; // if closed, s->sock becomes invalid as s is no longer dereferencable + if (FD_ISSET(sock,&rfds)) { + long n = (long)::recv(sock,buf,sizeof(buf),0); + if (n <= 0) { + this->close((PhySocket *)&(*s),true); + } else { + try { + _handler->phyOnTcpData((PhySocket *)&(*s),&(s->uptr),(void *)buf,(unsigned long)n); + } catch ( ... ) {} + } + } + if ((FD_ISSET(sock,&wfds))&&(FD_ISSET(sock,&_writefds))) { + try { + _handler->phyOnTcpWritable((PhySocket *)&(*s),&(s->uptr)); + } catch ( ... ) {} + } + } break; + + case ZT_PHY_SOCKET_TCP_LISTEN: + if (FD_ISSET(s->sock,&rfds)) { + memset(&ss,0,sizeof(ss)); + socklen_t slen = sizeof(ss); + ZT_PHY_SOCKFD_TYPE newSock = ::accept(s->sock,(struct sockaddr *)&ss,&slen); + if (ZT_PHY_SOCKFD_VALID(newSock)) { + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) { + ZT_PHY_CLOSE_SOCKET(newSock); + } else { +#if defined(_WIN32) || defined(_WIN64) + { BOOL f = (_noDelay ? TRUE : FALSE); setsockopt(newSock,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); } + { u_long iMode=1; ioctlsocket(newSock,FIONBIO,&iMode); } +#else + { int f = (_noDelay ? 1 : 0); setsockopt(newSock,IPPROTO_TCP,TCP_NODELAY,(char *)&f,sizeof(f)); } + fcntl(newSock,F_SETFL,O_NONBLOCK); +#endif + _socks.push_back(PhySocketImpl()); + PhySocketImpl &sws = _socks.back(); + FD_SET(newSock,&_readfds); + if ((long)newSock > _nfds) + _nfds = (long)newSock; + sws.type = ZT_PHY_SOCKET_TCP_IN; + sws.sock = newSock; + sws.uptr = (void *)0; + memcpy(&(sws.saddr),&ss,sizeof(struct sockaddr_storage)); + try { + _handler->phyOnTcpAccept((PhySocket *)&(*s),(PhySocket *)&(_socks.back()),&(s->uptr),&(sws.uptr),(const struct sockaddr *)&(sws.saddr)); + } catch ( ... ) {} + } + } + } + break; + + case ZT_PHY_SOCKET_UDP: + if (FD_ISSET(s->sock,&rfds)) { + for(;;) { + memset(&ss,0,sizeof(ss)); + socklen_t slen = sizeof(ss); + long n = (long)::recvfrom(s->sock,buf,sizeof(buf),0,(struct sockaddr *)&ss,&slen); + if (n > 0) { + try { + _handler->phyOnDatagram((PhySocket *)&(*s),&(s->uptr),(const struct sockaddr *)&(s->saddr),(const struct sockaddr *)&ss,(void *)buf,(unsigned long)n); + } catch ( ... ) {} + } else if (n < 0) + break; + } + } + break; + + case ZT_PHY_SOCKET_UNIX_IN: { +#ifdef __UNIX_LIKE__ + ZT_PHY_SOCKFD_TYPE sock = s->sock; // if closed, s->sock becomes invalid as s is no longer dereferencable + if ((FD_ISSET(sock,&wfds))&&(FD_ISSET(sock,&_writefds))) { + try { + _handler->phyOnUnixWritable((PhySocket *)&(*s),&(s->uptr),false); + } catch ( ... ) {} + } + if (FD_ISSET(sock,&rfds)) { + long n = (long)::read(sock,buf,sizeof(buf)); + if (n <= 0) { + this->close((PhySocket *)&(*s),true); + } else { + try { + _handler->phyOnUnixData((PhySocket *)&(*s),&(s->uptr),(void *)buf,(unsigned long)n); + } catch ( ... ) {} + } + } +#endif // __UNIX_LIKE__ + } break; + + case ZT_PHY_SOCKET_UNIX_LISTEN: +#ifdef __UNIX_LIKE__ + if (FD_ISSET(s->sock,&rfds)) { + memset(&ss,0,sizeof(ss)); + socklen_t slen = sizeof(ss); + ZT_PHY_SOCKFD_TYPE newSock = ::accept(s->sock,(struct sockaddr *)&ss,&slen); + if (ZT_PHY_SOCKFD_VALID(newSock)) { + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) { + ZT_PHY_CLOSE_SOCKET(newSock); + } else { + fcntl(newSock,F_SETFL,O_NONBLOCK); + _socks.push_back(PhySocketImpl()); + PhySocketImpl &sws = _socks.back(); + FD_SET(newSock,&_readfds); + if ((long)newSock > _nfds) + _nfds = (long)newSock; + sws.type = ZT_PHY_SOCKET_UNIX_IN; + sws.sock = newSock; + sws.uptr = (void *)0; + memcpy(&(sws.saddr),&ss,sizeof(struct sockaddr_storage)); + try { + //_handler->phyOnUnixAccept((PhySocket *)&(*s),(PhySocket *)&(_socks.back()),&(s->uptr),&(sws.uptr)); + } catch ( ... ) {} + } + } + } +#endif // __UNIX_LIKE__ + break; + + case ZT_PHY_SOCKET_FD: { + ZT_PHY_SOCKFD_TYPE sock = s->sock; + const bool readable = ((FD_ISSET(sock,&rfds))&&(FD_ISSET(sock,&_readfds))); + const bool writable = ((FD_ISSET(sock,&wfds))&&(FD_ISSET(sock,&_writefds))); + if ((readable)||(writable)) { + try { + //_handler->phyOnFileDescriptorActivity((PhySocket *)&(*s),&(s->uptr),readable,writable); + } catch ( ... ) {} + } + } break; + + default: + break; + + } + + if (s->type == ZT_PHY_SOCKET_CLOSED) + _socks.erase(s++); + else ++s; + } + } + + /** + * @param sock Socket to close + * @param callHandlers If true, call handlers for TCP connect (success: false) or close (default: true) + */ + inline void close(PhySocket *sock,bool callHandlers = true) + { + if (!sock) + return; + PhySocketImpl &sws = *(reinterpret_cast(sock)); + if (sws.type == ZT_PHY_SOCKET_CLOSED) + return; + + FD_CLR(sws.sock,&_readfds); + FD_CLR(sws.sock,&_writefds); +#if defined(_WIN32) || defined(_WIN64) + FD_CLR(sws.sock,&_exceptfds); +#endif + + if (sws.type != ZT_PHY_SOCKET_FD) + ZT_PHY_CLOSE_SOCKET(sws.sock); + +#ifdef __UNIX_LIKE__ + if (sws.type == ZT_PHY_SOCKET_UNIX_LISTEN) + ::unlink(((struct sockaddr_un *)(&(sws.saddr)))->sun_path); +#endif // __UNIX_LIKE__ + + if (callHandlers) { + switch(sws.type) { + case ZT_PHY_SOCKET_TCP_OUT_PENDING: + try { + _handler->phyOnTcpConnect(sock,&(sws.uptr),false); + } catch ( ... ) {} + break; + case ZT_PHY_SOCKET_TCP_OUT_CONNECTED: + case ZT_PHY_SOCKET_TCP_IN: + try { + _handler->phyOnTcpClose(sock,&(sws.uptr)); + } catch ( ... ) {} + break; + case ZT_PHY_SOCKET_UNIX_IN: +#ifdef __UNIX_LIKE__ + try { + _handler->phyOnUnixClose(sock,&(sws.uptr)); + } catch ( ... ) {} +#endif // __UNIX_LIKE__ + break; + default: + break; + } + } + + // Causes entry to be deleted from list in poll(), ignored elsewhere + sws.type = ZT_PHY_SOCKET_CLOSED; + + if ((long)sws.sock >= (long)_nfds) { + long nfds = (long)_whackSendSocket; + if ((long)_whackReceiveSocket > nfds) + nfds = (long)_whackReceiveSocket; + for(typename std::list::iterator s(_socks.begin());s!=_socks.end();++s) { + if ((s->type != ZT_PHY_SOCKET_CLOSED)&&((long)s->sock > nfds)) + nfds = (long)s->sock; + } + _nfds = nfds; + } + } +}; + +} // namespace ZeroTier + +#endif diff --git a/osdep/PortMapper.cpp b/osdep/PortMapper.cpp new file mode 100644 index 0000000..d3a1938 --- /dev/null +++ b/osdep/PortMapper.cpp @@ -0,0 +1,325 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifdef ZT_USE_MINIUPNPC + +// Uncomment to dump debug messages +//#define ZT_PORTMAPPER_TRACE 1 + +#include +#include +#include + +#include + +#include "../node/Utils.hpp" +#include "OSUtils.hpp" +#include "PortMapper.hpp" + +// These must be defined to get rid of dynamic export stuff in libminiupnpc and libnatpmp +#ifdef __WINDOWS__ +#ifndef MINIUPNP_STATICLIB +#define MINIUPNP_STATICLIB +#endif +#ifndef STATICLIB +#define STATICLIB +#endif +#endif + +#ifdef ZT_USE_SYSTEM_MINIUPNPC +#include +#include +#else +#include "../ext/miniupnpc/miniupnpc.h" +#include "../ext/miniupnpc/upnpcommands.h" +#endif + +#ifdef ZT_USE_SYSTEM_NATPMP +#include +#else +#include "../ext/libnatpmp/natpmp.h" +#endif + +namespace ZeroTier { + +class PortMapperImpl +{ +public: + PortMapperImpl(int localUdpPortToMap,const char *un) : + run(true), + localPort(localUdpPortToMap), + uniqueName(un) + { + } + + ~PortMapperImpl() {} + + void threadMain() + throw() + { + int mode = 0; // 0 == NAT-PMP, 1 == UPnP + +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"PortMapper: started for UDP port %d"ZT_EOL_S,localPort); +#endif + + while (run) { + + // --------------------------------------------------------------------- + // NAT-PMP mode (preferred) + // --------------------------------------------------------------------- + if (mode == 0) { + natpmp_t natpmp; + natpmpresp_t response; + int r = 0; + + bool natPmpSuccess = false; + for(int tries=0;tries<60;++tries) { + int tryPort = (int)localPort + tries; + if (tryPort >= 65535) + tryPort = (tryPort - 65535) + 1025; + + memset(&natpmp,0,sizeof(natpmp)); + memset(&response,0,sizeof(response)); + + if (initnatpmp(&natpmp,0,0) != 0) { + mode = 1; +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"PortMapper: NAT-PMP: init failed, switching to UPnP mode"ZT_EOL_S); +#endif + break; + } + + InetAddress publicAddress; + sendpublicaddressrequest(&natpmp); + uint64_t myTimeout = OSUtils::now() + 5000; + do { + fd_set fds; + struct timeval timeout; + FD_ZERO(&fds); + FD_SET(natpmp.s, &fds); + getnatpmprequesttimeout(&natpmp, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + r = readnatpmpresponseorretry(&natpmp, &response); + if (OSUtils::now() >= myTimeout) + break; + } while (r == NATPMP_TRYAGAIN); + if (r == 0) { + publicAddress = InetAddress((uint32_t)response.pnu.publicaddress.addr.s_addr,0); + } else { +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"PortMapper: NAT-PMP: request for external address failed, aborting..."ZT_EOL_S); +#endif + closenatpmp(&natpmp); + break; + } + + sendnewportmappingrequest(&natpmp,NATPMP_PROTOCOL_UDP,localPort,tryPort,(ZT_PORTMAPPER_REFRESH_DELAY * 2) / 1000); + myTimeout = OSUtils::now() + 10000; + do { + fd_set fds; + struct timeval timeout; + FD_ZERO(&fds); + FD_SET(natpmp.s, &fds); + getnatpmprequesttimeout(&natpmp, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + r = readnatpmpresponseorretry(&natpmp, &response); + if (OSUtils::now() >= myTimeout) + break; + } while (r == NATPMP_TRYAGAIN); + if (r == 0) { + publicAddress.setPort(response.pnu.newportmapping.mappedpublicport); +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"PortMapper: NAT-PMP: mapped %u to %s"ZT_EOL_S,(unsigned int)localPort,publicAddress.toString().c_str()); +#endif + Mutex::Lock sl(surface_l); + surface.clear(); + surface.push_back(publicAddress); + natPmpSuccess = true; + closenatpmp(&natpmp); + break; + } else { + closenatpmp(&natpmp); + // continue + } + } + + if (!natPmpSuccess) { + mode = 1; +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"PortMapper: NAT-PMP: request failed, switching to UPnP mode"ZT_EOL_S); +#endif + } + } + // --------------------------------------------------------------------- + + // --------------------------------------------------------------------- + // UPnP mode + // --------------------------------------------------------------------- + if (mode == 1) { + char lanaddr[4096]; + char externalip[4096]; // no range checking? so make these buffers larger than any UDP packet a uPnP server could send us as a precaution :P + char inport[16]; + char outport[16]; + struct UPNPUrls urls; + struct IGDdatas data; + + int upnpError = 0; + UPNPDev *devlist = upnpDiscoverAll(5000,(const char *)0,(const char *)0,0,0,2,&upnpError); + if (devlist) { + +#ifdef ZT_PORTMAPPER_TRACE + { + UPNPDev *dev = devlist; + while (dev) { + fprintf(stderr,"PortMapper: found UPnP device at URL '%s': %s"ZT_EOL_S,dev->descURL,dev->st); + dev = dev->pNext; + } + } +#endif + + memset(lanaddr,0,sizeof(lanaddr)); + memset(externalip,0,sizeof(externalip)); + memset(&urls,0,sizeof(urls)); + memset(&data,0,sizeof(data)); + Utils::snprintf(inport,sizeof(inport),"%d",localPort); + + if ((UPNP_GetValidIGD(devlist,&urls,&data,lanaddr,sizeof(lanaddr)))&&(lanaddr[0])) { +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"PortMapper: UPnP: my LAN IP address: %s"ZT_EOL_S,lanaddr); +#endif + if ((UPNP_GetExternalIPAddress(urls.controlURL,data.first.servicetype,externalip) == UPNPCOMMAND_SUCCESS)&&(externalip[0])) { +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"PortMapper: UPnP: my external IP address: %s"ZT_EOL_S,externalip); +#endif + + for(int tries=0;tries<60;++tries) { + int tryPort = (int)localPort + tries; + if (tryPort >= 65535) + tryPort = (tryPort - 65535) + 1025; + Utils::snprintf(outport,sizeof(outport),"%u",tryPort); + + // First check and see if this port is already mapped to the + // same unique name. If so, keep this mapping and don't try + // to map again since this can break buggy routers. But don't + // fail if this command fails since not all routers support it. + { + char haveIntClient[128]; // 128 == big enough for all these as per miniupnpc "documentation" + char haveIntPort[128]; + char haveDesc[128]; + char haveEnabled[128]; + char haveLeaseDuration[128]; + memset(haveIntClient,0,sizeof(haveIntClient)); + memset(haveIntPort,0,sizeof(haveIntPort)); + memset(haveDesc,0,sizeof(haveDesc)); + memset(haveEnabled,0,sizeof(haveEnabled)); + memset(haveLeaseDuration,0,sizeof(haveLeaseDuration)); + if ((UPNP_GetSpecificPortMappingEntry(urls.controlURL,data.first.servicetype,outport,"UDP",(const char *)0,haveIntClient,haveIntPort,haveDesc,haveEnabled,haveLeaseDuration) == UPNPCOMMAND_SUCCESS)&&(uniqueName == haveDesc)) { +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"PortMapper: UPnP: reusing previously reserved external port: %s"ZT_EOL_S,outport); +#endif + Mutex::Lock sl(surface_l); + surface.clear(); + InetAddress tmp(externalip); + tmp.setPort(tryPort); + surface.push_back(tmp); + break; + } + } + + // Try to map this port + int mapResult = 0; + if ((mapResult = UPNP_AddPortMapping(urls.controlURL,data.first.servicetype,outport,inport,lanaddr,uniqueName.c_str(),"UDP",(const char *)0,"0")) == UPNPCOMMAND_SUCCESS) { +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"PortMapper: UPnP: reserved external port: %s"ZT_EOL_S,outport); +#endif + Mutex::Lock sl(surface_l); + surface.clear(); + InetAddress tmp(externalip); + tmp.setPort(tryPort); + surface.push_back(tmp); + break; + } else { +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"PortMapper: UPnP: UPNP_AddPortMapping(%s) failed: %d"ZT_EOL_S,outport,mapResult); +#endif + Thread::sleep(1000); + } + } + + } else { + mode = 0; +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"PortMapper: UPnP: UPNP_GetExternalIPAddress failed, returning to NAT-PMP mode"ZT_EOL_S); +#endif + } + } else { + mode = 0; +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"PortMapper: UPnP: UPNP_GetValidIGD failed, returning to NAT-PMP mode"ZT_EOL_S); +#endif + } + + freeUPNPDevlist(devlist); + + } else { + mode = 0; +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"PortMapper: upnpDiscover failed, returning to NAT-PMP mode: %d"ZT_EOL_S,upnpError); +#endif + } + } + // --------------------------------------------------------------------- + +#ifdef ZT_PORTMAPPER_TRACE + fprintf(stderr,"UPNPClient: rescanning in %d ms"ZT_EOL_S,ZT_PORTMAPPER_REFRESH_DELAY); +#endif + Thread::sleep(ZT_PORTMAPPER_REFRESH_DELAY); + } + + delete this; + } + + volatile bool run; + int localPort; + std::string uniqueName; + + Mutex surface_l; + std::vector surface; +}; + +PortMapper::PortMapper(int localUdpPortToMap,const char *uniqueName) +{ + _impl = new PortMapperImpl(localUdpPortToMap,uniqueName); + Thread::start(_impl); +} + +PortMapper::~PortMapper() +{ + _impl->run = false; +} + +std::vector PortMapper::get() const +{ + Mutex::Lock _l(_impl->surface_l); + return _impl->surface; +} + +} // namespace ZeroTier + +#endif // ZT_USE_MINIUPNPC diff --git a/osdep/PortMapper.hpp b/osdep/PortMapper.hpp new file mode 100644 index 0000000..0b8d15f --- /dev/null +++ b/osdep/PortMapper.hpp @@ -0,0 +1,71 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifdef ZT_USE_MINIUPNPC + +#ifndef ZT_PORTMAPPER_HPP +#define ZT_PORTMAPPER_HPP + +#include + +#include "../node/Constants.hpp" +#include "../node/InetAddress.hpp" +#include "../node/Mutex.hpp" +#include "Thread.hpp" + +/** + * How frequently should we refresh our UPNP/NAT-PnP/whatever state? + */ +#define ZT_PORTMAPPER_REFRESH_DELAY 300000 + +namespace ZeroTier { + +class PortMapperImpl; + +/** + * UPnP/NAT-PnP port mapping "daemon" + */ +class PortMapper +{ + friend class PortMapperImpl; + +public: + /** + * Create and start port mapper service + * + * @param localUdpPortToMap Port we want visible to the outside world + * @param name Unique name of this endpoint (based on ZeroTier address) + */ + PortMapper(int localUdpPortToMap,const char *uniqueName); + + ~PortMapper(); + + /** + * @return All current external mappings for our port + */ + std::vector get() const; + +private: + PortMapperImpl *_impl; +}; + +} // namespace ZeroTier + +#endif + +#endif // ZT_USE_MINIUPNPC diff --git a/osdep/README.md b/osdep/README.md new file mode 100644 index 0000000..a77297a --- /dev/null +++ b/osdep/README.md @@ -0,0 +1,6 @@ +OS-Dependent and OS-Interface Things +====== + +This folder contains stuff that interfaces with the base operating system +like Phy for network access and the various OS-specific Ethernet tap +drivers. diff --git a/osdep/Thread.hpp b/osdep/Thread.hpp new file mode 100644 index 0000000..227c2cf --- /dev/null +++ b/osdep/Thread.hpp @@ -0,0 +1,205 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_THREAD_HPP +#define ZT_THREAD_HPP + +#include + +#include "../node/Constants.hpp" + +#ifdef __WINDOWS__ + +#include +#include +#include + +#include "../node/Mutex.hpp" + +namespace ZeroTier { + +template +static DWORD WINAPI ___zt_threadMain(LPVOID lpParam) +{ + try { + ((C *)lpParam)->threadMain(); + } catch ( ... ) {} + return 0; +} + +class Thread +{ +public: + Thread() + throw() + { + _th = NULL; + _tid = 0; + } + + template + static inline Thread start(C *instance) + throw(std::runtime_error) + { + Thread t; + t._th = CreateThread(NULL,0,&___zt_threadMain,(LPVOID)instance,0,&t._tid); + if (t._th == NULL) + throw std::runtime_error("CreateThread() failed"); + return t; + } + + static inline void join(const Thread &t) + { + if (t._th != NULL) { + for(;;) { + DWORD ec = STILL_ACTIVE; + GetExitCodeThread(t._th,&ec); + if (ec == STILL_ACTIVE) + WaitForSingleObject(t._th,1000); + else break; + } + } + } + + static inline void sleep(unsigned long ms) + { + Sleep((DWORD)ms); + } + + // Not available on *nix platforms + static inline void cancelIO(const Thread &t) + { + if (t._th != NULL) + CancelSynchronousIo(t._th); + } + + inline operator bool() const throw() { return (_th != NULL); } + +private: + HANDLE _th; + DWORD _tid; +}; + +} // namespace ZeroTier + +#else + +#include +#include +#include +#include +#include + +namespace ZeroTier { + +template +static void *___zt_threadMain(void *instance) +{ + try { + ((C *)instance)->threadMain(); + } catch ( ... ) {} + return (void *)0; +} + +/** + * A thread identifier, and static methods to start and join threads + */ +class Thread +{ +public: + Thread() + throw() + { + memset(&_tid,0,sizeof(_tid)); + pthread_attr_init(&_tattr); + // This corrects for systems with abnormally small defaults (musl) and also + // shrinks the stack on systems with large defaults to save a bit of memory. + pthread_attr_setstacksize(&_tattr,ZT_THREAD_MIN_STACK_SIZE); + _started = false; + } + + ~Thread() + { + pthread_attr_destroy(&_tattr); + } + + Thread(const Thread &t) + throw() + { + memcpy(&_tid,&(t._tid),sizeof(_tid)); + _started = t._started; + } + + inline Thread &operator=(const Thread &t) + throw() + { + memcpy(&_tid,&(t._tid),sizeof(_tid)); + _started = t._started; + return *this; + } + + /** + * Start a new thread + * + * @param instance Instance whose threadMain() method gets called by new thread + * @return Thread identifier + * @throws std::runtime_error Unable to create thread + * @tparam C Class containing threadMain() + */ + template + static inline Thread start(C *instance) + throw(std::runtime_error) + { + Thread t; + t._started = true; + if (pthread_create(&t._tid,&t._tattr,&___zt_threadMain,instance)) + throw std::runtime_error("pthread_create() failed, unable to create thread"); + return t; + } + + /** + * Join to a thread, waiting for it to terminate (does nothing on null Thread values) + * + * @param t Thread to join + */ + static inline void join(const Thread &t) + { + if (t._started) + pthread_join(t._tid,(void **)0); + } + + /** + * Sleep the current thread + * + * @param ms Number of milliseconds to sleep + */ + static inline void sleep(unsigned long ms) { usleep(ms * 1000); } + + inline operator bool() const throw() { return (_started); } + +private: + pthread_t _tid; + pthread_attr_t _tattr; + volatile bool _started; +}; + +} // namespace ZeroTier + +#endif // __WINDOWS__ / !__WINDOWS__ + +#endif diff --git a/osdep/WindowsEthernetTap.cpp b/osdep/WindowsEthernetTap.cpp new file mode 100644 index 0000000..79b9d35 --- /dev/null +++ b/osdep/WindowsEthernetTap.cpp @@ -0,0 +1,1222 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" +#include "../node/Mutex.hpp" + +#include "WindowsEthernetTap.hpp" +#include "OSUtils.hpp" + +#include "..\windows\TapDriver6\tap-windows.h" + +// Create a fake unused default route to force detection of network type on networks without gateways +#define ZT_WINDOWS_CREATE_FAKE_DEFAULT_ROUTE + +// Function signatures of dynamically loaded functions, from newdev.h, setupapi.h, and cfgmgr32.h +typedef BOOL (WINAPI *UpdateDriverForPlugAndPlayDevicesA_t)(_In_opt_ HWND hwndParent,_In_ LPCSTR HardwareId,_In_ LPCSTR FullInfPath,_In_ DWORD InstallFlags,_Out_opt_ PBOOL bRebootRequired); +typedef BOOL (WINAPI *SetupDiGetINFClassA_t)(_In_ PCSTR InfName,_Out_ LPGUID ClassGuid,_Out_writes_(ClassNameSize) PSTR ClassName,_In_ DWORD ClassNameSize,_Out_opt_ PDWORD RequiredSize); +typedef HDEVINFO (WINAPI *SetupDiCreateDeviceInfoList_t)(_In_opt_ CONST GUID *ClassGuid,_In_opt_ HWND hwndParent); +typedef BOOL (WINAPI *SetupDiCreateDeviceInfoA_t)(_In_ HDEVINFO DeviceInfoSet,_In_ PCSTR DeviceName,_In_ CONST GUID *ClassGuid,_In_opt_ PCSTR DeviceDescription,_In_opt_ HWND hwndParent,_In_ DWORD CreationFlags,_Out_opt_ PSP_DEVINFO_DATA DeviceInfoData); +typedef BOOL (WINAPI *SetupDiSetDeviceRegistryPropertyA_t)(_In_ HDEVINFO DeviceInfoSet,_Inout_ PSP_DEVINFO_DATA DeviceInfoData,_In_ DWORD Property,_In_reads_bytes_opt_(PropertyBufferSize) CONST BYTE *PropertyBuffer,_In_ DWORD PropertyBufferSize); +typedef BOOL (WINAPI *SetupDiCallClassInstaller_t)(_In_ DI_FUNCTION InstallFunction,_In_ HDEVINFO DeviceInfoSet,_In_opt_ PSP_DEVINFO_DATA DeviceInfoData); +typedef BOOL (WINAPI *SetupDiDestroyDeviceInfoList_t)(_In_ HDEVINFO DeviceInfoSet); +typedef HDEVINFO (WINAPI *SetupDiGetClassDevsExA_t)(_In_opt_ CONST GUID *ClassGuid,_In_opt_ PCSTR Enumerator,_In_opt_ HWND hwndParent,_In_ DWORD Flags,_In_opt_ HDEVINFO DeviceInfoSet,_In_opt_ PCSTR MachineName,_Reserved_ PVOID Reserved); +typedef BOOL (WINAPI *SetupDiOpenDeviceInfoA_t)(_In_ HDEVINFO DeviceInfoSet,_In_ PCSTR DeviceInstanceId,_In_opt_ HWND hwndParent,_In_ DWORD OpenFlags,_Out_opt_ PSP_DEVINFO_DATA DeviceInfoData); +typedef BOOL (WINAPI *SetupDiEnumDeviceInfo_t)(_In_ HDEVINFO DeviceInfoSet,_In_ DWORD MemberIndex,_Out_ PSP_DEVINFO_DATA DeviceInfoData); +typedef BOOL (WINAPI *SetupDiSetClassInstallParamsA_t)(_In_ HDEVINFO DeviceInfoSet,_In_opt_ PSP_DEVINFO_DATA DeviceInfoData,_In_reads_bytes_opt_(ClassInstallParamsSize) PSP_CLASSINSTALL_HEADER ClassInstallParams,_In_ DWORD ClassInstallParamsSize); +typedef CONFIGRET (WINAPI *CM_Get_Device_ID_ExA_t)(_In_ DEVINST dnDevInst,_Out_writes_(BufferLen) PSTR Buffer,_In_ ULONG BufferLen,_In_ ULONG ulFlags,_In_opt_ HMACHINE hMachine); +typedef BOOL (WINAPI *SetupDiGetDeviceInstanceIdA_t)(_In_ HDEVINFO DeviceInfoSet,_In_ PSP_DEVINFO_DATA DeviceInfoData,_Out_writes_opt_(DeviceInstanceIdSize) PSTR DeviceInstanceId,_In_ DWORD DeviceInstanceIdSize,_Out_opt_ PDWORD RequiredSize); + +namespace ZeroTier { + +namespace { + +// Static/singleton class that when initialized loads a bunch of environment information and a few dynamically loaded DLLs +class WindowsEthernetTapEnv +{ +public: + WindowsEthernetTapEnv() + { +#ifdef _WIN64 + is64Bit = TRUE; + tapDriverPath = "\\tap-windows\\x64\\zttap300.inf"; +#else + is64Bit = FALSE; + IsWow64Process(GetCurrentProcess(),&is64Bit); + if (is64Bit) { + fprintf(stderr,"FATAL: you must use the 64-bit ZeroTier One service on 64-bit Windows systems\r\n"); + _exit(1); + } + tapDriverPath = "\\tap-windows\\x86\\zttap300.inf"; +#endif + tapDriverName = "zttap300"; + + setupApiMod = LoadLibraryA("setupapi.dll"); + if (!setupApiMod) { + fprintf(stderr,"FATAL: unable to dynamically load setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiGetINFClassA = (SetupDiGetINFClassA_t)GetProcAddress(setupApiMod,"SetupDiGetINFClassA"))) { + fprintf(stderr,"FATAL: SetupDiGetINFClassA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiCreateDeviceInfoList = (SetupDiCreateDeviceInfoList_t)GetProcAddress(setupApiMod,"SetupDiCreateDeviceInfoList"))) { + fprintf(stderr,"FATAL: SetupDiCreateDeviceInfoList not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiCreateDeviceInfoA = (SetupDiCreateDeviceInfoA_t)GetProcAddress(setupApiMod,"SetupDiCreateDeviceInfoA"))) { + fprintf(stderr,"FATAL: SetupDiCreateDeviceInfoA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiSetDeviceRegistryPropertyA = (SetupDiSetDeviceRegistryPropertyA_t)GetProcAddress(setupApiMod,"SetupDiSetDeviceRegistryPropertyA"))) { + fprintf(stderr,"FATAL: SetupDiSetDeviceRegistryPropertyA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiCallClassInstaller = (SetupDiCallClassInstaller_t)GetProcAddress(setupApiMod,"SetupDiCallClassInstaller"))) { + fprintf(stderr,"FATAL: SetupDiCallClassInstaller not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiDestroyDeviceInfoList = (SetupDiDestroyDeviceInfoList_t)GetProcAddress(setupApiMod,"SetupDiDestroyDeviceInfoList"))) { + fprintf(stderr,"FATAL: SetupDiDestroyDeviceInfoList not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiGetClassDevsExA = (SetupDiGetClassDevsExA_t)GetProcAddress(setupApiMod,"SetupDiGetClassDevsExA"))) { + fprintf(stderr,"FATAL: SetupDiGetClassDevsExA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiOpenDeviceInfoA = (SetupDiOpenDeviceInfoA_t)GetProcAddress(setupApiMod,"SetupDiOpenDeviceInfoA"))) { + fprintf(stderr,"FATAL: SetupDiOpenDeviceInfoA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiEnumDeviceInfo = (SetupDiEnumDeviceInfo_t)GetProcAddress(setupApiMod,"SetupDiEnumDeviceInfo"))) { + fprintf(stderr,"FATAL: SetupDiEnumDeviceInfo not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiSetClassInstallParamsA = (SetupDiSetClassInstallParamsA_t)GetProcAddress(setupApiMod,"SetupDiSetClassInstallParamsA"))) { + fprintf(stderr,"FATAL: SetupDiSetClassInstallParamsA not found in setupapi.dll\r\n"); + _exit(1); + } + if (!(this->SetupDiGetDeviceInstanceIdA = (SetupDiGetDeviceInstanceIdA_t)GetProcAddress(setupApiMod,"SetupDiGetDeviceInstanceIdA"))) { + fprintf(stderr,"FATAL: SetupDiGetDeviceInstanceIdA not found in setupapi.dll\r\n"); + _exit(1); + } + + newDevMod = LoadLibraryA("newdev.dll"); + if (!newDevMod) { + fprintf(stderr,"FATAL: unable to dynamically load newdev.dll\r\n"); + _exit(1); + } + if (!(this->UpdateDriverForPlugAndPlayDevicesA = (UpdateDriverForPlugAndPlayDevicesA_t)GetProcAddress(newDevMod,"UpdateDriverForPlugAndPlayDevicesA"))) { + fprintf(stderr,"FATAL: UpdateDriverForPlugAndPlayDevicesA not found in newdev.dll\r\n"); + _exit(1); + } + + cfgMgrMod = LoadLibraryA("cfgmgr32.dll"); + if (!cfgMgrMod) { + fprintf(stderr,"FATAL: unable to dynamically load cfgmgr32.dll\r\n"); + _exit(1); + } + if (!(this->CM_Get_Device_ID_ExA = (CM_Get_Device_ID_ExA_t)GetProcAddress(cfgMgrMod,"CM_Get_Device_ID_ExA"))) { + fprintf(stderr,"FATAL: CM_Get_Device_ID_ExA not found in cfgmgr32.dll\r\n"); + _exit(1); + } + } + + BOOL is64Bit; // is the system 64-bit, regardless of whether this binary is or not + std::string tapDriverPath; + std::string tapDriverName; + + UpdateDriverForPlugAndPlayDevicesA_t UpdateDriverForPlugAndPlayDevicesA; + + SetupDiGetINFClassA_t SetupDiGetINFClassA; + SetupDiCreateDeviceInfoList_t SetupDiCreateDeviceInfoList; + SetupDiCreateDeviceInfoA_t SetupDiCreateDeviceInfoA; + SetupDiSetDeviceRegistryPropertyA_t SetupDiSetDeviceRegistryPropertyA; + SetupDiCallClassInstaller_t SetupDiCallClassInstaller; + SetupDiDestroyDeviceInfoList_t SetupDiDestroyDeviceInfoList; + SetupDiGetClassDevsExA_t SetupDiGetClassDevsExA; + SetupDiOpenDeviceInfoA_t SetupDiOpenDeviceInfoA; + SetupDiEnumDeviceInfo_t SetupDiEnumDeviceInfo; + SetupDiSetClassInstallParamsA_t SetupDiSetClassInstallParamsA; + SetupDiGetDeviceInstanceIdA_t SetupDiGetDeviceInstanceIdA; + + CM_Get_Device_ID_ExA_t CM_Get_Device_ID_ExA; + +private: + HMODULE setupApiMod; + HMODULE newDevMod; + HMODULE cfgMgrMod; +}; +static const WindowsEthernetTapEnv WINENV; + +// Only create or delete devices one at a time +static Mutex _systemTapInitLock; + +// Only perform installation or uninstallation options one at a time +static Mutex _systemDeviceManagementLock; + +} // anonymous namespace + +std::string WindowsEthernetTap::addNewPersistentTapDevice(const char *pathToInf,std::string &deviceInstanceId) +{ + Mutex::Lock _l(_systemDeviceManagementLock); + + GUID classGuid; + char className[1024]; + if (!WINENV.SetupDiGetINFClassA(pathToInf,&classGuid,className,sizeof(className),(PDWORD)0)) { + return std::string("SetupDiGetINFClassA() failed -- unable to read zttap driver INF file"); + } + + HDEVINFO deviceInfoSet = WINENV.SetupDiCreateDeviceInfoList(&classGuid,(HWND)0); + if (deviceInfoSet == INVALID_HANDLE_VALUE) { + return std::string("SetupDiCreateDeviceInfoList() failed"); + } + + SP_DEVINFO_DATA deviceInfoData; + memset(&deviceInfoData,0,sizeof(deviceInfoData)); + deviceInfoData.cbSize = sizeof(deviceInfoData); + if (!WINENV.SetupDiCreateDeviceInfoA(deviceInfoSet,className,&classGuid,(PCSTR)0,(HWND)0,DICD_GENERATE_ID,&deviceInfoData)) { + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + return std::string("SetupDiCreateDeviceInfoA() failed"); + } + + if (!WINENV.SetupDiSetDeviceRegistryPropertyA(deviceInfoSet,&deviceInfoData,SPDRP_HARDWAREID,(const BYTE *)WINENV.tapDriverName.c_str(),(DWORD)(WINENV.tapDriverName.length() + 1))) { + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + return std::string("SetupDiSetDeviceRegistryPropertyA() failed"); + } + + if (!WINENV.SetupDiCallClassInstaller(DIF_REGISTERDEVICE,deviceInfoSet,&deviceInfoData)) { + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + return std::string("SetupDiCallClassInstaller(DIF_REGISTERDEVICE) failed"); + } + + // HACK: During upgrades, this can fail while the installer is still running. So make 60 attempts + // with a 1s delay between each attempt. + bool driverInstalled = false; + for(int retryCounter=0;retryCounter<60;++retryCounter) { + BOOL rebootRequired = FALSE; + if (WINENV.UpdateDriverForPlugAndPlayDevicesA((HWND)0,WINENV.tapDriverName.c_str(),pathToInf,INSTALLFLAG_FORCE|INSTALLFLAG_NONINTERACTIVE,&rebootRequired)) { + driverInstalled = true; + break; + } else Sleep(1000); + } + if (!driverInstalled) { + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + return std::string("UpdateDriverForPlugAndPlayDevices() failed (made 60 attempts)"); + } + + char iidbuf[1024]; + DWORD iidReqSize = sizeof(iidbuf); + if (WINENV.SetupDiGetDeviceInstanceIdA(deviceInfoSet,&deviceInfoData,iidbuf,sizeof(iidbuf),&iidReqSize)) { + deviceInstanceId = iidbuf; + } // failure here is not fatal since we only need this on Vista and 2008 -- other versions fill it into the registry automatically + + WINENV.SetupDiDestroyDeviceInfoList(deviceInfoSet); + + return std::string(); +} + +std::string WindowsEthernetTap::destroyAllLegacyPersistentTapDevices() +{ + char subkeyName[1024]; + char subkeyClass[1024]; + char data[1024]; + + std::set instanceIdPathsToRemove; + { + HKEY nwAdapters; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}",0,KEY_READ|KEY_WRITE,&nwAdapters) != ERROR_SUCCESS) + return std::string("Could not open registry key"); + + for(DWORD subkeyIndex=0;;++subkeyIndex) { + DWORD type; + DWORD dataLen; + DWORD subkeyNameLen = sizeof(subkeyName); + DWORD subkeyClassLen = sizeof(subkeyClass); + FILETIME lastWriteTime; + if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + data[dataLen] = '\0'; + + if ((!strnicmp(data,"zttap",5))&&(WINENV.tapDriverName != data)) { + std::string instanceIdPath; + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) + instanceIdPath.assign(data,dataLen); + if (instanceIdPath.length() != 0) + instanceIdPathsToRemove.insert(instanceIdPath); + } + } + } else break; // end of list or failure + } + + RegCloseKey(nwAdapters); + } + + std::string errlist; + for(std::set::iterator iidp(instanceIdPathsToRemove.begin());iidp!=instanceIdPathsToRemove.end();++iidp) { + std::string err = deletePersistentTapDevice(iidp->c_str()); + if (err.length() > 0) { + if (errlist.length() > 0) + errlist.push_back(','); + errlist.append(err); + } + } + return errlist; +} + +std::string WindowsEthernetTap::destroyAllPersistentTapDevices() +{ + char subkeyName[1024]; + char subkeyClass[1024]; + char data[1024]; + + std::set instanceIdPathsToRemove; + { + HKEY nwAdapters; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}",0,KEY_READ|KEY_WRITE,&nwAdapters) != ERROR_SUCCESS) + return std::string("Could not open registry key"); + + for(DWORD subkeyIndex=0;;++subkeyIndex) { + DWORD type; + DWORD dataLen; + DWORD subkeyNameLen = sizeof(subkeyName); + DWORD subkeyClassLen = sizeof(subkeyClass); + FILETIME lastWriteTime; + if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + data[dataLen] = '\0'; + + if (!strnicmp(data,"zttap",5)) { + std::string instanceIdPath; + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) + instanceIdPath.assign(data,dataLen); + if (instanceIdPath.length() != 0) + instanceIdPathsToRemove.insert(instanceIdPath); + } + } + } else break; // end of list or failure + } + + RegCloseKey(nwAdapters); + } + + std::string errlist; + for(std::set::iterator iidp(instanceIdPathsToRemove.begin());iidp!=instanceIdPathsToRemove.end();++iidp) { + std::string err = deletePersistentTapDevice(iidp->c_str()); + if (err.length() > 0) { + if (errlist.length() > 0) + errlist.push_back(','); + errlist.append(err); + } + } + return errlist; +} + +std::string WindowsEthernetTap::deletePersistentTapDevice(const char *instanceId) +{ + char iid[256]; + SP_REMOVEDEVICE_PARAMS rmdParams; + + memset(&rmdParams,0,sizeof(rmdParams)); + rmdParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); + rmdParams.ClassInstallHeader.InstallFunction = DIF_REMOVE; + rmdParams.Scope = DI_REMOVEDEVICE_GLOBAL; + rmdParams.HwProfile = 0; + + Mutex::Lock _l(_systemDeviceManagementLock); + + HDEVINFO devInfo = WINENV.SetupDiGetClassDevsExA((const GUID *)0,(PCSTR)0,(HWND)0,DIGCF_ALLCLASSES,(HDEVINFO)0,(PCSTR)0,(PVOID)0); + if (devInfo == INVALID_HANDLE_VALUE) + return std::string("SetupDiGetClassDevsExA() failed"); + WINENV.SetupDiOpenDeviceInfoA(devInfo,instanceId,(HWND)0,0,(PSP_DEVINFO_DATA)0); + + SP_DEVINFO_DATA devInfoData; + memset(&devInfoData,0,sizeof(devInfoData)); + devInfoData.cbSize = sizeof(devInfoData); + for(DWORD devIndex=0;WINENV.SetupDiEnumDeviceInfo(devInfo,devIndex,&devInfoData);devIndex++) { + if ((WINENV.CM_Get_Device_ID_ExA(devInfoData.DevInst,iid,sizeof(iid),0,(HMACHINE)0) == CR_SUCCESS)&&(!strcmp(iid,instanceId))) { + if (!WINENV.SetupDiSetClassInstallParamsA(devInfo,&devInfoData,&rmdParams.ClassInstallHeader,sizeof(rmdParams))) { + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return std::string("SetupDiSetClassInstallParams() failed"); + } + + if (!WINENV.SetupDiCallClassInstaller(DIF_REMOVE,devInfo,&devInfoData)) { + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return std::string("SetupDiCallClassInstaller(DIF_REMOVE) failed"); + } + + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return std::string(); + } + } + + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return std::string("instance ID not found"); +} + +bool WindowsEthernetTap::setPersistentTapDeviceState(const char *instanceId,bool enabled) +{ + char iid[256]; + SP_PROPCHANGE_PARAMS params; + + Mutex::Lock _l(_systemDeviceManagementLock); + + HDEVINFO devInfo = WINENV.SetupDiGetClassDevsExA((const GUID *)0,(PCSTR)0,(HWND)0,DIGCF_ALLCLASSES,(HDEVINFO)0,(PCSTR)0,(PVOID)0); + if (devInfo == INVALID_HANDLE_VALUE) + return false; + WINENV.SetupDiOpenDeviceInfoA(devInfo,instanceId,(HWND)0,0,(PSP_DEVINFO_DATA)0); + + SP_DEVINFO_DATA devInfoData; + memset(&devInfoData,0,sizeof(devInfoData)); + devInfoData.cbSize = sizeof(devInfoData); + for(DWORD devIndex=0;WINENV.SetupDiEnumDeviceInfo(devInfo,devIndex,&devInfoData);devIndex++) { + if ((WINENV.CM_Get_Device_ID_ExA(devInfoData.DevInst,iid,sizeof(iid),0,(HMACHINE)0) == CR_SUCCESS)&&(!strcmp(iid,instanceId))) { + memset(¶ms,0,sizeof(params)); + params.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); + params.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE; + params.StateChange = enabled ? DICS_ENABLE : DICS_DISABLE; + params.Scope = DICS_FLAG_GLOBAL; + params.HwProfile = 0; + + WINENV.SetupDiSetClassInstallParamsA(devInfo,&devInfoData,¶ms.ClassInstallHeader,sizeof(params)); + WINENV.SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,devInfo,&devInfoData); + + memset(¶ms,0,sizeof(params)); + params.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); + params.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE; + params.StateChange = enabled ? DICS_ENABLE : DICS_DISABLE; + params.Scope = DICS_FLAG_CONFIGSPECIFIC; + params.HwProfile = 0; + + WINENV.SetupDiSetClassInstallParamsA(devInfo,&devInfoData,¶ms.ClassInstallHeader,sizeof(params)); + WINENV.SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,devInfo,&devInfoData); + + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return true; + } + } + + WINENV.SetupDiDestroyDeviceInfoList(devInfo); + return false; +} + +WindowsEthernetTap::WindowsEthernetTap( + const char *hp, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *friendlyName, + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void *arg) : + _handler(handler), + _arg(arg), + _mac(mac), + _nwid(nwid), + _tap(INVALID_HANDLE_VALUE), + _injectSemaphore(INVALID_HANDLE_VALUE), + _pathToHelpers(hp), + _run(true), + _initialized(false), + _enabled(true) +{ + char subkeyName[1024]; + char subkeyClass[1024]; + char data[1024]; + char tag[24]; + std::string mySubkeyName; + + if (mtu > 2800) + throw std::runtime_error("MTU too large."); + + // We "tag" registry entries with the network ID to identify persistent devices + Utils::snprintf(tag,sizeof(tag),"%.16llx",(unsigned long long)nwid); + + Mutex::Lock _l(_systemTapInitLock); + + HKEY nwAdapters; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}",0,KEY_READ|KEY_WRITE,&nwAdapters) != ERROR_SUCCESS) + throw std::runtime_error("unable to open registry key for network adapter enumeration"); + + // Look for the tap instance that corresponds with this network + for(DWORD subkeyIndex=0;;++subkeyIndex) { + DWORD type; + DWORD dataLen; + DWORD subkeyNameLen = sizeof(subkeyName); + DWORD subkeyClassLen = sizeof(subkeyClass); + FILETIME lastWriteTime; + if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + data[dataLen] = (char)0; + + if (WINENV.tapDriverName == data) { + std::string instanceId; + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"NetCfgInstanceId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) + instanceId.assign(data,dataLen); + + std::string instanceIdPath; + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) + instanceIdPath.assign(data,dataLen); + + if ((_netCfgInstanceId.length() == 0)&&(instanceId.length() != 0)&&(instanceIdPath.length() != 0)) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"_ZeroTierTapIdentifier",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + data[dataLen] = '\0'; + if (!strcmp(data,tag)) { + _netCfgInstanceId = instanceId; + _deviceInstanceId = instanceIdPath; + + mySubkeyName = subkeyName; + break; // found it! + } + } + } + } + } + } else break; // no more subkeys or error occurred enumerating them + } + + // If there is no device, try to create one + bool creatingNewDevice = (_netCfgInstanceId.length() == 0); + std::string newDeviceInstanceId; + if (creatingNewDevice) { + for(int getNewAttemptCounter=0;getNewAttemptCounter<2;++getNewAttemptCounter) { + for(DWORD subkeyIndex=0;;++subkeyIndex) { + DWORD type; + DWORD dataLen; + DWORD subkeyNameLen = sizeof(subkeyName); + DWORD subkeyClassLen = sizeof(subkeyClass); + FILETIME lastWriteTime; + if (RegEnumKeyExA(nwAdapters,subkeyIndex,subkeyName,&subkeyNameLen,(DWORD *)0,subkeyClass,&subkeyClassLen,&lastWriteTime) == ERROR_SUCCESS) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"ComponentId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + data[dataLen] = '\0'; + + if (WINENV.tapDriverName == data) { + type = 0; + dataLen = sizeof(data); + if ((RegGetValueA(nwAdapters,subkeyName,"_ZeroTierTapIdentifier",RRF_RT_ANY,&type,(PVOID)data,&dataLen) != ERROR_SUCCESS)||(dataLen <= 0)) { + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"NetCfgInstanceId",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) { + RegSetKeyValueA(nwAdapters,subkeyName,"_ZeroTierTapIdentifier",REG_SZ,tag,(DWORD)(strlen(tag)+1)); + + _netCfgInstanceId.assign(data,dataLen); + + type = 0; + dataLen = sizeof(data); + if (RegGetValueA(nwAdapters,subkeyName,"DeviceInstanceID",RRF_RT_ANY,&type,(PVOID)data,&dataLen) == ERROR_SUCCESS) + _deviceInstanceId.assign(data,dataLen); + + mySubkeyName = subkeyName; + + // Disable DHCP by default on new devices + HKEY tcpIpInterfaces; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces",0,KEY_READ|KEY_WRITE,&tcpIpInterfaces) == ERROR_SUCCESS) { + DWORD enable = 0; + RegSetKeyValueA(tcpIpInterfaces,_netCfgInstanceId.c_str(),"EnableDHCP",REG_DWORD,&enable,sizeof(enable)); + RegCloseKey(tcpIpInterfaces); + } + + break; // found an unused zttap device + } + } + } + } + } else break; // no more keys or error occurred + } + + if (_netCfgInstanceId.length() > 0) { + break; // found an unused zttap device + } else { + // no unused zttap devices, so create one + std::string errm = addNewPersistentTapDevice((std::string(_pathToHelpers) + WINENV.tapDriverPath).c_str(),newDeviceInstanceId); + if (errm.length() > 0) + throw std::runtime_error(std::string("unable to create new device instance: ")+errm); + } + } + } + + if (_netCfgInstanceId.length() > 0) { + char tmps[64]; + unsigned int tmpsl = Utils::snprintf(tmps,sizeof(tmps),"%.2X-%.2X-%.2X-%.2X-%.2X-%.2X",(unsigned int)mac[0],(unsigned int)mac[1],(unsigned int)mac[2],(unsigned int)mac[3],(unsigned int)mac[4],(unsigned int)mac[5]) + 1; + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"NetworkAddress",REG_SZ,tmps,tmpsl); + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"MAC",REG_SZ,tmps,tmpsl); + tmpsl = Utils::snprintf(tmps, sizeof(tmps), "%d", mtu); + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"MTU",REG_SZ,tmps,tmpsl); + + DWORD tmp = 0; + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"*NdisDeviceType",REG_DWORD,(LPCVOID)&tmp,sizeof(tmp)); + tmp = IF_TYPE_ETHERNET_CSMACD; + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"*IfType",REG_DWORD,(LPCVOID)&tmp,sizeof(tmp)); + + if (creatingNewDevice) { + // Vista/2008 does not set this + if (newDeviceInstanceId.length() > 0) + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"DeviceInstanceID",REG_SZ,newDeviceInstanceId.c_str(),(DWORD)newDeviceInstanceId.length()); + + // Set EnableDHCP to 0 by default on new devices + tmp = 0; + RegSetKeyValueA(nwAdapters,mySubkeyName.c_str(),"EnableDHCP",REG_DWORD,(LPCVOID)&tmp,sizeof(tmp)); + } + RegCloseKey(nwAdapters); + } else { + RegCloseKey(nwAdapters); + throw std::runtime_error("unable to find or create tap adapter"); + } + + { + char nobraces[128]; // strip braces from GUID before converting it, because Windows + const char *nbtmp1 = _netCfgInstanceId.c_str(); + char *nbtmp2 = nobraces; + while (*nbtmp1) { + if ((*nbtmp1 != '{')&&(*nbtmp1 != '}')) + *nbtmp2++ = *nbtmp1; + ++nbtmp1; + } + *nbtmp2 = (char)0; + if (UuidFromStringA((RPC_CSTR)nobraces,&_deviceGuid) != RPC_S_OK) + throw std::runtime_error("unable to convert instance ID GUID to native GUID (invalid NetCfgInstanceId in registry?)"); + } + + // Get the LUID, which is one of like four fucking ways to refer to a network device in Windows + if (ConvertInterfaceGuidToLuid(&_deviceGuid,&_deviceLuid) != NO_ERROR) + throw std::runtime_error("unable to convert device interface GUID to LUID"); + + //_initialized = true; + + if (friendlyName) + setFriendlyName(friendlyName); + + _injectSemaphore = CreateSemaphore(NULL,0,1,NULL); + _thread = Thread::start(this); +} + +WindowsEthernetTap::~WindowsEthernetTap() +{ + _run = false; + ReleaseSemaphore(_injectSemaphore,1,NULL); + Thread::join(_thread); + CloseHandle(_injectSemaphore); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),false); +} + +void WindowsEthernetTap::setEnabled(bool en) +{ + _enabled = en; +} + +bool WindowsEthernetTap::enabled() const +{ + return _enabled; +} + +bool WindowsEthernetTap::addIp(const InetAddress &ip) +{ + if (!ip.netmaskBits()) // sanity check... netmask of 0.0.0.0 is WUT? + return false; + + Mutex::Lock _l(_assignedIps_m); + if (std::find(_assignedIps.begin(),_assignedIps.end(),ip) != _assignedIps.end()) + return true; + _assignedIps.push_back(ip); + _syncIps(); + return true; +} + +bool WindowsEthernetTap::removeIp(const InetAddress &ip) +{ + if (ip.isV6()) + return true; + + { + Mutex::Lock _l(_assignedIps_m); + std::vector::iterator aip(std::find(_assignedIps.begin(),_assignedIps.end(),ip)); + if (aip != _assignedIps.end()) + _assignedIps.erase(aip); + } + + if (!_initialized) + return false; + + try { + MIB_UNICASTIPADDRESS_TABLE *ipt = (MIB_UNICASTIPADDRESS_TABLE *)0; + if (GetUnicastIpAddressTable(AF_UNSPEC,&ipt) == NO_ERROR) { + if ((ipt)&&(ipt->NumEntries > 0)) { + for(DWORD i=0;i<(DWORD)ipt->NumEntries;++i) { + if (ipt->Table[i].InterfaceLuid.Value == _deviceLuid.Value) { + InetAddress addr; + switch(ipt->Table[i].Address.si_family) { + case AF_INET: + addr.set(&(ipt->Table[i].Address.Ipv4.sin_addr.S_un.S_addr),4,ipt->Table[i].OnLinkPrefixLength); + break; + case AF_INET6: + addr.set(ipt->Table[i].Address.Ipv6.sin6_addr.u.Byte,16,ipt->Table[i].OnLinkPrefixLength); + if (addr.ipScope() == InetAddress::IP_SCOPE_LINK_LOCAL) + continue; // can't remove link-local IPv6 addresses + break; + } + if (addr == ip) { + DeleteUnicastIpAddressEntry(&(ipt->Table[i])); + FreeMibTable(ipt); + + if (ip.isV4()) { + std::vector regIps(_getRegistryIPv4Value("IPAddress")); + std::vector regSubnetMasks(_getRegistryIPv4Value("SubnetMask")); + std::string ipstr(ip.toIpString()); + for (std::vector::iterator rip(regIps.begin()), rm(regSubnetMasks.begin()); ((rip != regIps.end()) && (rm != regSubnetMasks.end())); ++rip, ++rm) { + if (*rip == ipstr) { + regIps.erase(rip); + regSubnetMasks.erase(rm); + _setRegistryIPv4Value("IPAddress", regIps); + _setRegistryIPv4Value("SubnetMask", regSubnetMasks); + break; + } + } + } + + return true; + } + } + } + } + FreeMibTable((PVOID)ipt); + } + } catch ( ... ) {} + return false; +} + +std::vector WindowsEthernetTap::ips() const +{ + static const InetAddress linkLocalLoopback("fe80::1",64); // what is this and why does Windows assign it? + std::vector addrs; + + if (!_initialized) + return addrs; + + try { + MIB_UNICASTIPADDRESS_TABLE *ipt = (MIB_UNICASTIPADDRESS_TABLE *)0; + if (GetUnicastIpAddressTable(AF_UNSPEC,&ipt) == NO_ERROR) { + if ((ipt)&&(ipt->NumEntries > 0)) { + for(DWORD i=0;i<(DWORD)ipt->NumEntries;++i) { + if (ipt->Table[i].InterfaceLuid.Value == _deviceLuid.Value) { + switch(ipt->Table[i].Address.si_family) { + case AF_INET: { + InetAddress ip(&(ipt->Table[i].Address.Ipv4.sin_addr.S_un.S_addr),4,ipt->Table[i].OnLinkPrefixLength); + if (ip != InetAddress::LO4) + addrs.push_back(ip); + } break; + case AF_INET6: { + InetAddress ip(ipt->Table[i].Address.Ipv6.sin6_addr.u.Byte,16,ipt->Table[i].OnLinkPrefixLength); + if ((ip != linkLocalLoopback)&&(ip != InetAddress::LO6)) + addrs.push_back(ip); + } break; + } + } + } + } + FreeMibTable(ipt); + } + } catch ( ... ) {} // sanity check, shouldn't happen unless out of memory + + std::sort(addrs.begin(),addrs.end()); + addrs.erase(std::unique(addrs.begin(),addrs.end()),addrs.end()); + + return addrs; +} + +void WindowsEthernetTap::put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len) +{ + if ((!_initialized)||(!_enabled)||(_tap == INVALID_HANDLE_VALUE)||(len > (ZT_IF_MTU))) + return; + + Mutex::Lock _l(_injectPending_m); + _injectPending.push( std::pair,unsigned int>(Array(),len + 14) ); + char *d = _injectPending.back().first.data; + to.copyTo(d,6); + from.copyTo(d + 6,6); + d[12] = (char)((etherType >> 8) & 0xff); + d[13] = (char)(etherType & 0xff); + memcpy(d + 14,data,len); + + ReleaseSemaphore(_injectSemaphore,1,NULL); +} + +std::string WindowsEthernetTap::deviceName() const +{ + char tmp[1024]; + if (ConvertInterfaceLuidToNameA(&_deviceLuid,tmp,sizeof(tmp)) != NO_ERROR) + return std::string("[ConvertInterfaceLuidToName() failed]"); + return std::string(tmp); +} + +void WindowsEthernetTap::setFriendlyName(const char *dn) +{ + if (!_initialized) + return; + HKEY ifp; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,(std::string("SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\") + _netCfgInstanceId).c_str(),0,KEY_READ|KEY_WRITE,&ifp) == ERROR_SUCCESS) { + RegSetKeyValueA(ifp,"Connection","Name",REG_SZ,(LPCVOID)dn,(DWORD)(strlen(dn)+1)); + RegCloseKey(ifp); + } +} + +void WindowsEthernetTap::scanMulticastGroups(std::vector &added,std::vector &removed) +{ + if (!_initialized) + return; + HANDLE t = _tap; + if (t == INVALID_HANDLE_VALUE) + return; + + std::vector newGroups; + + // The ZT1 tap driver supports an IOCTL to get multicast memberships at the L2 + // level... something Windows does not seem to expose ordinarily. This lets + // pretty much anything work... IPv4, IPv6, IPX, oldskool Netbios, who knows... + unsigned char mcastbuf[TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS_OUTPUT_BUF_SIZE]; + DWORD bytesReturned = 0; + if (DeviceIoControl(t,TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS,(LPVOID)0,0,(LPVOID)mcastbuf,sizeof(mcastbuf),&bytesReturned,NULL)) { + if ((bytesReturned > 0)&&(bytesReturned <= TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS_OUTPUT_BUF_SIZE)) { // sanity check + MAC mac; + DWORD i = 0; + while ((i + 6) <= bytesReturned) { + mac.setTo(mcastbuf + i,6); + i += 6; + if ((mac.isMulticast())&&(!mac.isBroadcast())) { + // exclude the nulls that may be returned or any other junk Windows puts in there + newGroups.push_back(MulticastGroup(mac,0)); + } + } + } + } + + std::vector allIps(ips()); + for(std::vector::iterator ip(allIps.begin());ip!=allIps.end();++ip) + newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip)); + + std::sort(newGroups.begin(),newGroups.end()); + newGroups.erase(std::unique(newGroups.begin(),newGroups.end()),newGroups.end()); + + for(std::vector::iterator m(newGroups.begin());m!=newGroups.end();++m) { + if (!std::binary_search(_multicastGroups.begin(),_multicastGroups.end(),*m)) + added.push_back(*m); + } + for(std::vector::iterator m(_multicastGroups.begin());m!=_multicastGroups.end();++m) { + if (!std::binary_search(newGroups.begin(),newGroups.end(),*m)) + removed.push_back(*m); + } + + _multicastGroups.swap(newGroups); +} + +NET_IFINDEX WindowsEthernetTap::interfaceIndex() const +{ + NET_IFINDEX idx = -1; + if (ConvertInterfaceLuidToIndex(&_deviceLuid,&idx) == NO_ERROR) + return idx; + return -1; +} + +void WindowsEthernetTap::threadMain() + throw() +{ + char tapReadBuf[ZT_IF_MTU + 32]; + char tapPath[128]; + HANDLE wait4[3]; + OVERLAPPED tapOvlRead,tapOvlWrite; + + Utils::snprintf(tapPath,sizeof(tapPath),"\\\\.\\Global\\%s.tap",_netCfgInstanceId.c_str()); + + try { + while (_run) { + // Because Windows + Sleep(250); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),false); + Sleep(250); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),true); + Sleep(250); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),false); + Sleep(250); + setPersistentTapDeviceState(_deviceInstanceId.c_str(),true); + Sleep(250); + + _tap = CreateFileA(tapPath,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM|FILE_FLAG_OVERLAPPED,NULL); + if (_tap == INVALID_HANDLE_VALUE) { + Sleep(250); + continue; + } + + { + uint32_t tmpi = 1; + DWORD bytesReturned = 0; + DeviceIoControl(_tap,TAP_WIN_IOCTL_SET_MEDIA_STATUS,&tmpi,sizeof(tmpi),&tmpi,sizeof(tmpi),&bytesReturned,NULL); + } + +#ifdef ZT_WINDOWS_CREATE_FAKE_DEFAULT_ROUTE + { + /* This inserts a fake default route and a fake ARP entry, forcing + * Windows to detect this as a "real" network and apply proper + * firewall rules. + * + * This hack is completely stupid, but Windows made me do it + * by being broken and insane. + * + * Background: Windows tries to detect its network location by + * matching it to the ARP address of the default route. Networks + * without default routes are "unidentified networks" and cannot + * have their firewall classification changed by the user (easily). + * + * Yes, you read that right. + * + * The common workaround is to set *NdisDeviceType to 1, which + * totally disables all Windows firewall functionality. This is + * the answer you'll find on most forums for things like OpenVPN. + * + * Yes, you read that right. + * + * The default route workaround is also known, but for this to + * work there must be a known default IP that resolves to a known + * ARP address. This works for an OpenVPN tunnel, but not here + * because this isn't a tunnel. It's a mesh. There is no "other + * end," or any other known always on IP. + * + * So let's make a fake one and shove it in there along with its + * fake static ARP entry. Also makes it instant-on and static. + * + * We'll have to see what DHCP does with this. In the future we + * probably will not want to do this on DHCP-enabled networks, so + * when we enable DHCP we will go in and yank this wacko hacko from + * the routing table before doing so. + * + * Like Jesse Pinkman would say: "YEEEEAAH BITCH!" */ + const uint32_t fakeIp = htonl(0x19fffffe); // 25.255.255.254 -- unrouted IPv4 block + for(int i=0;i<8;++i) { + MIB_IPNET_ROW2 ipnr; + memset(&ipnr,0,sizeof(ipnr)); + ipnr.Address.si_family = AF_INET; + ipnr.Address.Ipv4.sin_addr.s_addr = fakeIp; + ipnr.InterfaceLuid.Value = _deviceLuid.Value; + ipnr.PhysicalAddress[0] = _mac[0] ^ 0x10; // just make something up that's consistent and not part of this net + ipnr.PhysicalAddress[1] = 0x00; + ipnr.PhysicalAddress[2] = (UCHAR)((_deviceGuid.Data1 >> 24) & 0xff); + ipnr.PhysicalAddress[3] = (UCHAR)((_deviceGuid.Data1 >> 16) & 0xff); + ipnr.PhysicalAddress[4] = (UCHAR)((_deviceGuid.Data1 >> 8) & 0xff); + ipnr.PhysicalAddress[5] = (UCHAR)(_deviceGuid.Data1 & 0xff); + ipnr.PhysicalAddressLength = 6; + ipnr.State = NlnsPermanent; + ipnr.IsRouter = 1; + ipnr.IsUnreachable = 0; + ipnr.ReachabilityTime.LastReachable = 0x0fffffff; + ipnr.ReachabilityTime.LastUnreachable = 1; + DWORD result = CreateIpNetEntry2(&ipnr); + if (result != NO_ERROR) + Sleep(250); + else break; + } + for(int i=0;i<8;++i) { + MIB_IPFORWARD_ROW2 nr; + memset(&nr,0,sizeof(nr)); + InitializeIpForwardEntry(&nr); + nr.InterfaceLuid.Value = _deviceLuid.Value; + nr.DestinationPrefix.Prefix.si_family = AF_INET; // rest is left as 0.0.0.0/0 + nr.NextHop.si_family = AF_INET; + nr.NextHop.Ipv4.sin_addr.s_addr = fakeIp; + nr.Metric = 9999; // do not use as real default route + nr.Protocol = MIB_IPPROTO_NETMGMT; + DWORD result = CreateIpForwardEntry2(&nr); + if (result != NO_ERROR) + Sleep(250); + else break; + } + } +#endif + + // Assign or re-assign any should-be-assigned IPs in case we have restarted + { + Mutex::Lock _l(_assignedIps_m); + _syncIps(); + } + + memset(&tapOvlRead,0,sizeof(tapOvlRead)); + tapOvlRead.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); + memset(&tapOvlWrite,0,sizeof(tapOvlWrite)); + tapOvlWrite.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); + + wait4[0] = _injectSemaphore; + wait4[1] = tapOvlRead.hEvent; + wait4[2] = tapOvlWrite.hEvent; // only included if writeInProgress is true + + ReadFile(_tap,tapReadBuf,sizeof(tapReadBuf),NULL,&tapOvlRead); + bool writeInProgress = false; + ULONGLONG timeOfLastBorkCheck = GetTickCount64(); + + + _initialized = true; + + while (_run) { + DWORD waitResult = WaitForMultipleObjectsEx(writeInProgress ? 3 : 2,wait4,FALSE,2500,TRUE); + if (!_run) break; // will also break outer while(_run) + + // Check for issues with adapter and close/reopen if any are detected. This + // check fixes a while boatload of Windows adapter 'coma' issues after + // sleep/wake and when adapters are added/removed. Basically if the tap + // device is borked, whack it. + { + ULONGLONG tc = GetTickCount64(); + if ((tc - timeOfLastBorkCheck) >= 2500) { + timeOfLastBorkCheck = tc; + char aabuf[16384]; + ULONG aalen = sizeof(aabuf); + if (GetAdaptersAddresses(AF_UNSPEC,GAA_FLAG_SKIP_UNICAST|GAA_FLAG_SKIP_ANYCAST|GAA_FLAG_SKIP_MULTICAST|GAA_FLAG_SKIP_DNS_SERVER|GAA_FLAG_SKIP_FRIENDLY_NAME,(void *)0,reinterpret_cast(aabuf),&aalen) == NO_ERROR) { + bool isBorked = false; + + PIP_ADAPTER_ADDRESSES aa = reinterpret_cast(aabuf); + while (aa) { + if (_deviceLuid.Value == aa->Luid.Value) { + isBorked = (aa->OperStatus != IfOperStatusUp); + break; + } + aa = aa->Next; + } + + if (isBorked) { + // Close and reopen tap device if there's an issue (outer loop) + break; + } + } + } + } + + if ((waitResult == WAIT_TIMEOUT)||(waitResult == WAIT_FAILED)) { + Sleep(250); // guard against spinning under some conditions + continue; + } + + if (HasOverlappedIoCompleted(&tapOvlRead)) { + DWORD bytesRead = 0; + if (GetOverlappedResult(_tap,&tapOvlRead,&bytesRead,FALSE)) { + if ((bytesRead > 14)&&(_enabled)) { + MAC to(tapReadBuf,6); + MAC from(tapReadBuf + 6,6); + unsigned int etherType = ((((unsigned int)tapReadBuf[12]) & 0xff) << 8) | (((unsigned int)tapReadBuf[13]) & 0xff); + try { + _handler(_arg,(void *)0,_nwid,from,to,etherType,0,tapReadBuf + 14,bytesRead - 14); + } catch ( ... ) {} // handlers should not throw + } + } + ReadFile(_tap,tapReadBuf,ZT_IF_MTU + 32,NULL,&tapOvlRead); + } + + if (writeInProgress) { + if (HasOverlappedIoCompleted(&tapOvlWrite)) { + writeInProgress = false; + _injectPending_m.lock(); + _injectPending.pop(); + } else continue; // still writing, so skip code below and wait + } else _injectPending_m.lock(); + + if (!_injectPending.empty()) { + WriteFile(_tap,_injectPending.front().first.data,_injectPending.front().second,NULL,&tapOvlWrite); + writeInProgress = true; + } + + _injectPending_m.unlock(); + } + + CancelIo(_tap); + + CloseHandle(tapOvlRead.hEvent); + CloseHandle(tapOvlWrite.hEvent); + CloseHandle(_tap); + _tap = INVALID_HANDLE_VALUE; + + // We will restart and re-open the tap unless _run == false + } + } catch ( ... ) {} // catch unexpected exceptions -- this should not happen but would prevent program crash or other weird issues since threads should not throw +} + +NET_IFINDEX WindowsEthernetTap::_getDeviceIndex() +{ + MIB_IF_TABLE2 *ift = (MIB_IF_TABLE2 *)0; + + if (GetIfTable2Ex(MibIfTableRaw,&ift) != NO_ERROR) + throw std::runtime_error("GetIfTable2Ex() failed"); + + if (ift->NumEntries > 0) { + for(ULONG i=0;iNumEntries;++i) { + if (ift->Table[i].InterfaceLuid.Value == _deviceLuid.Value) { + NET_IFINDEX idx = ift->Table[i].InterfaceIndex; + FreeMibTable(ift); + return idx; + } + } + } + + FreeMibTable(&ift); + + throw std::runtime_error("interface not found"); +} + +std::vector WindowsEthernetTap::_getRegistryIPv4Value(const char *regKey) +{ + std::vector value; + HKEY tcpIpInterfaces; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces",0,KEY_READ|KEY_WRITE,&tcpIpInterfaces) == ERROR_SUCCESS) { + char buf[16384]; + DWORD len = sizeof(buf); + DWORD kt = REG_MULTI_SZ; + if (RegGetValueA(tcpIpInterfaces,_netCfgInstanceId.c_str(),regKey,0,&kt,&buf,&len) == ERROR_SUCCESS) { + switch(kt) { + case REG_SZ: + if (len > 0) + value.push_back(std::string(buf)); + break; + case REG_MULTI_SZ: { + for(DWORD k=0,s=0;k &value) +{ + std::string regMulti; + for(std::vector::const_iterator s(value.begin());s!=value.end();++s) { + regMulti.append(*s); + regMulti.push_back((char)0); + } + HKEY tcpIpInterfaces; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,"SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces",0,KEY_READ|KEY_WRITE,&tcpIpInterfaces) == ERROR_SUCCESS) { + if (regMulti.length() > 0) { + regMulti.push_back((char)0); + RegSetKeyValueA(tcpIpInterfaces,_netCfgInstanceId.c_str(),regKey,REG_MULTI_SZ,regMulti.data(),(DWORD)regMulti.length()); + } else { + RegDeleteKeyValueA(tcpIpInterfaces,_netCfgInstanceId.c_str(),regKey); + } + RegCloseKey(tcpIpInterfaces); + } +} + +void WindowsEthernetTap::_syncIps() +{ + // assumes _assignedIps_m is locked + + if (!_initialized) + return; + + std::vector haveIps(ips()); + + for(std::vector::const_iterator aip(_assignedIps.begin());aip!=_assignedIps.end();++aip) { + if (std::find(haveIps.begin(),haveIps.end(),*aip) == haveIps.end()) { + MIB_UNICASTIPADDRESS_ROW ipr; + + InitializeUnicastIpAddressEntry(&ipr); + if (aip->isV4()) { + ipr.Address.Ipv4.sin_family = AF_INET; + ipr.Address.Ipv4.sin_addr.S_un.S_addr = *((const uint32_t *)aip->rawIpData()); + ipr.OnLinkPrefixLength = aip->netmaskBits(); + if (ipr.OnLinkPrefixLength >= 32) + continue; + } else if (aip->isV6()) { + ipr.Address.Ipv6.sin6_family = AF_INET6; + memcpy(ipr.Address.Ipv6.sin6_addr.u.Byte,aip->rawIpData(),16); + ipr.OnLinkPrefixLength = aip->netmaskBits(); + if (ipr.OnLinkPrefixLength >= 128) + continue; + } else continue; + + ipr.PrefixOrigin = IpPrefixOriginManual; + ipr.SuffixOrigin = IpSuffixOriginManual; + ipr.ValidLifetime = 0xffffffff; + ipr.PreferredLifetime = 0xffffffff; + + ipr.InterfaceLuid = _deviceLuid; + ipr.InterfaceIndex = _getDeviceIndex(); + + CreateUnicastIpAddressEntry(&ipr); + } + + if (aip->isV4()) + { + std::string ipStr(aip->toIpString()); + std::vector regIps(_getRegistryIPv4Value("IPAddress")); + if (std::find(regIps.begin(), regIps.end(), ipStr) == regIps.end()) { + std::vector regSubnetMasks(_getRegistryIPv4Value("SubnetMask")); + regIps.push_back(ipStr); + regSubnetMasks.push_back(aip->netmask().toIpString()); + _setRegistryIPv4Value("IPAddress", regIps); + _setRegistryIPv4Value("SubnetMask", regSubnetMasks); + } + } + } +} + +} // namespace ZeroTier diff --git a/osdep/WindowsEthernetTap.hpp b/osdep/WindowsEthernetTap.hpp new file mode 100644 index 0000000..f2cf73f --- /dev/null +++ b/osdep/WindowsEthernetTap.hpp @@ -0,0 +1,152 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_WINDOWSETHERNETTAP_HPP +#define ZT_WINDOWSETHERNETTAP_HPP + +#include +#include + +#include + +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Mutex.hpp" +#include "../node/Array.hpp" +#include "../node/MulticastGroup.hpp" +#include "../node/InetAddress.hpp" +#include "../osdep/Thread.hpp" + +namespace ZeroTier { + +class WindowsEthernetTap +{ +public: + /** + * Installs a new instance of the ZT tap driver + * + * @param pathToInf Path to zttap driver .inf file + * @param deviceInstanceId Buffer to fill with device instance ID on success (and if SetupDiGetDeviceInstanceIdA succeeds, which it should) + * @return Empty string on success, otherwise an error message + */ + static std::string addNewPersistentTapDevice(const char *pathToInf,std::string &deviceInstanceId); + + /** + * Uninstalls all persistent tap devices that have legacy drivers + * + * @return Empty string on success, otherwise an error message + */ + static std::string destroyAllLegacyPersistentTapDevices(); + + /** + * Uninstalls all persistent tap devices on the system + * + * @return Empty string on success, otherwise an error message + */ + static std::string destroyAllPersistentTapDevices(); + + /** + * Uninstall a specific persistent tap device by instance ID + * + * @param instanceId Device instance ID + * @return Empty string on success, otherwise an error message + */ + static std::string deletePersistentTapDevice(const char *instanceId); + + /** + * Disable a persistent tap device by instance ID + * + * @param instanceId Device instance ID + * @param enabled Enable device? + * @return True if device was found and disabled + */ + static bool setPersistentTapDeviceState(const char *instanceId,bool enabled); + + WindowsEthernetTap( + const char *hp, + const MAC &mac, + unsigned int mtu, + unsigned int metric, + uint64_t nwid, + const char *friendlyName, + void (*handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int), + void *arg); + + ~WindowsEthernetTap(); + + void setEnabled(bool en); + bool enabled() const; + bool addIp(const InetAddress &ip); + bool removeIp(const InetAddress &ip); + std::vector ips() const; + void put(const MAC &from,const MAC &to,unsigned int etherType,const void *data,unsigned int len); + std::string deviceName() const; + void setFriendlyName(const char *friendlyName); + void scanMulticastGroups(std::vector &added,std::vector &removed); + + inline const NET_LUID &luid() const { return _deviceLuid; } + inline const GUID &guid() const { return _deviceGuid; } + inline const std::string &instanceId() const { return _deviceInstanceId; } + NET_IFINDEX interfaceIndex() const; + + void threadMain() + throw(); + + bool isInitialized() const { return _initialized; }; + +private: + NET_IFINDEX _getDeviceIndex(); // throws on failure + std::vector _getRegistryIPv4Value(const char *regKey); + void _setRegistryIPv4Value(const char *regKey,const std::vector &value); + void _syncIps(); + + void (*_handler)(void *,void *,uint64_t,const MAC &,const MAC &,unsigned int,unsigned int,const void *,unsigned int); + void *_arg; + MAC _mac; + uint64_t _nwid; + Thread _thread; + + volatile HANDLE _tap; + HANDLE _injectSemaphore; + + GUID _deviceGuid; + NET_LUID _deviceLuid; + std::string _netCfgInstanceId; + std::string _deviceInstanceId; + + std::vector _assignedIps; // IPs assigned with addIp + Mutex _assignedIps_m; + + std::vector _multicastGroups; + + std::queue< std::pair< Array,unsigned int > > _injectPending; + Mutex _injectPending_m; + + std::string _pathToHelpers; + + volatile bool _run; + volatile bool _initialized; + volatile bool _enabled; +}; + +} // namespace ZeroTier + +#endif diff --git a/root-watcher/README.md b/root-watcher/README.md new file mode 100644 index 0000000..ded6a63 --- /dev/null +++ b/root-watcher/README.md @@ -0,0 +1,8 @@ +Root Server Watcher +====== + +This is a small daemon written in NodeJS that watches a set of root servers and records peer status information into a Postgres database. + +To use type `npm install` to install modules. Then edit `config.json.example` and rename to `config.json`. For each of your roots you will need to configure a way for this script to reach it. You will also need to use `schema.sql` to initialize a Postgres database to contain your logs and set it up in `config.json` as well. + +This doesn't (yet) include any software for reading the log database and doing anything useful with the information inside, though given that it's a simple SQL database it should not be hard to compose queries to show interesting statistics. diff --git a/root-watcher/config.json.example b/root-watcher/config.json.example new file mode 100644 index 0000000..0ad1bbe --- /dev/null +++ b/root-watcher/config.json.example @@ -0,0 +1,30 @@ +{ + "interval": 30000, + "dbSaveInterval": 60000, + "peerTimeout": 600000, + "db": { + "database": "ztr", + "user": "postgres", + "password": "s00p3rs3kr3t", + "host": "127.0.0.1", + "port": 5432, + "max": 16, + "idleTimeoutMillis": 30000 + }, + "roots": { + "my-root-01": { + "id": 1, + "ip": "10.0.0.1", + "port": 9993, + "authToken": "foobarbaz", + "peers": "/peer" + }, + "my-root-02": { + "id": 2, + "ip": "10.0.0.2", + "port": 9993, + "authToken": "lalafoo", + "peers": "/peer" + } + } +} diff --git a/root-watcher/package.json b/root-watcher/package.json new file mode 100644 index 0000000..d6e86d7 --- /dev/null +++ b/root-watcher/package.json @@ -0,0 +1,16 @@ +{ + "name": "zerotier-root-watcher", + "version": "1.0.0", + "description": "Simple background service to watch a cluster of roots and record peer info into a database", + "main": "zerotier-root-watcher.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "ZeroTier, Inc. ", + "license": "GPL-3.0", + "dependencies": { + "async": "^2.3.0", + "pg": "^6.1.5", + "zlib": "^1.0.5" + } +} diff --git a/root-watcher/schema.sql b/root-watcher/schema.sql new file mode 100644 index 0000000..bdb3a1c --- /dev/null +++ b/root-watcher/schema.sql @@ -0,0 +1,21 @@ +/* Schema for ZeroTier root watcher log database */ + +/* If you cluster this DB using any PG clustering scheme that uses logs, you must remove UNLOGGED here! */ +CREATE UNLOGGED TABLE "Peer" +( + "ztAddress" BIGINT NOT NULL, + "timestamp" BIGINT NOT NULL, + "versionMajor" INTEGER NOT NULL, + "versionMinor" INTEGER NOT NULL, + "versionRev" INTEGER NOT NULL, + "rootId" INTEGER NOT NULL, + "phyPort" INTEGER NOT NULL, + "phyLinkQuality" REAL NOT NULL, + "phyLastReceive" BIGINT NOT NULL, + "phyAddress" INET NOT NULL +); + +CREATE INDEX "Peer_ztAddress" ON "Peer" ("ztAddress"); +CREATE INDEX "Peer_timestamp" ON "Peer" ("timestamp"); +CREATE INDEX "Peer_rootId" ON "Peer" ("rootId"); +CREATE INDEX "Peer_phyAddress" ON "Peer" ("phyAddress"); diff --git a/root-watcher/zerotier-root-watcher.js b/root-watcher/zerotier-root-watcher.js new file mode 100644 index 0000000..d4607fc --- /dev/null +++ b/root-watcher/zerotier-root-watcher.js @@ -0,0 +1,235 @@ +'use strict'; + +const pg = require('pg'); +const zlib = require('zlib'); +const http = require('http'); +const fs = require('fs'); +const async = require('async'); + +const config = JSON.parse(fs.readFileSync('./config.json')); +const roots = config.roots||{}; + +const db = new pg.Pool(config.db); + +process.on('uncaughtException',function(err) { + console.error('ERROR: uncaught exception: '+err); + if (err.stack) + console.error(err.stack); +}); + +function httpRequest(host,port,authToken,method,path,args,callback) +{ + var responseBody = []; + var postData = (args) ? JSON.stringify(args) : null; + + var req = http.request({ + host: host, + port: port, + path: path, + method: method, + headers: { + 'X-ZT1-Auth': (authToken||''), + 'Content-Length': (postData) ? postData.length : 0 + } + },function(res) { + res.on('data',function(chunk) { + if ((chunk)&&(chunk.length > 0)) + responseBody.push(chunk); + }); + res.on('timeout',function() { + try { + if (typeof callback === 'function') { + var cb = callback; + callback = null; + cb(new Error('connection timed out'),null); + } + req.abort(); + } catch (e) {} + }); + res.on('error',function(e) { + try { + if (typeof callback === 'function') { + var cb = callback; + callback = null; + cb(new Error('connection timed out'),null); + } + req.abort(); + } catch (e) {} + }); + res.on('end',function() { + if (typeof callback === 'function') { + var cb = callback; + callback = null; + if (responseBody.length === 0) { + return cb(null,{}); + } else { + responseBody = Buffer.concat(responseBody); + + if (responseBody.length < 2) { + return cb(null,{}); + } + + if ((responseBody.readUInt8(0,true) === 0x1f)&&(responseBody.readUInt8(1,true) === 0x8b)) { + try { + responseBody = zlib.gunzipSync(responseBody); + } catch (e) { + return cb(e,null); + } + } + + try { + return cb(null,JSON.parse(responseBody)); + } catch (e) { + return cb(e,null); + } + } + } + }); + }).on('error',function(e) { + try { + if (typeof callback === 'function') { + var cb = callback; + callback = null; + cb(e,null); + } + req.abort(); + } catch (e) {} + }).on('timeout',function() { + try { + if (typeof callback === 'function') { + var cb = callback; + callback = null; + cb(new Error('connection timed out'),null); + } + req.abort(); + } catch (e) {} + }); + + req.setTimeout(30000); + req.setNoDelay(true); + + if (postData !== null) + req.end(postData); + else req.end(); +}; + +var peerStatus = {}; + +function saveToDb() +{ + db.connect(function(err,client,clientDone) { + if (err) { + console.log('WARNING: database error writing peers: '+err.toString()); + clientDone(); + return setTimeout(saveToDb,config.dbSaveInterval||60000); + } + client.query('BEGIN',function(err) { + if (err) { + console.log('WARNING: database error writing peers: '+err.toString()); + clientDone(); + return setTimeout(saveToDb,config.dbSaveInterval||60000); + } + let timeout = Date.now() - (config.peerTimeout||600000); + let wtotal = 0; + async.eachSeries(Object.keys(peerStatus),function(address,nextAddress) { + let s = peerStatus[address]; + if (s[1] <= timeout) { + delete peerStatus[address]; + return process.nextTick(nextAddress); + } else { + ++wtotal; + client.query('INSERT INTO "Peer" ("ztAddress","timestamp","versionMajor","versionMinor","versionRev","rootId","phyPort","phyLinkQuality","phyLastReceive","phyAddress") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)',s,nextAddress); + } + },function(err) { + if (err) + console.log('WARNING database error writing peers: '+err.toString()); + console.log(Date.now().toString()+' '+wtotal); + client.query('COMMIT',function(err,result) { + clientDone(); + return setTimeout(saveToDb,config.dbSaveInterval||60000); + }); + }); + }); + }); +}; + +function doRootUpdate(name,id,ip,port,peersPath,authToken,interval) +{ + httpRequest(ip,port,authToken,"GET",peersPath,null,function(err,res) { + if (err) { + console.log('WARNING: cannot reach '+name+peersPath+' (will try again in 1s): '+err.toString()); + return setTimeout(function() { doRootUpdate(name,id,ip,port,peersPath,authToken,interval); },1000); + } + if (!Array.isArray(res)) { + console.log('WARNING: cannot reach '+name+peersPath+' (will try again in 1s): response is not an array of peers'); + return setTimeout(function() { doRootUpdate(name,id,ip,port,peersPath,authToken,interval); },1000); + } + + //console.log(name+': '+res.length+' peer entries.'); + let now = Date.now(); + let count = 0; + for(let pi=0;pi 0)) { + let bestPath = null; + for(let i=0;i 0)&&((!bestPath)||(bestPath.lastReceive < lr))) + bestPath = paths[i]; + } + } + + if (bestPath) { + let a = bestPath.address; + if (typeof a === 'string') { + let a2 = a.split('/'); + if (a2.length === 2) { + let vmaj = peer.versionMajor; + if ((typeof vmaj === 'undefined')||(vmaj === null)) vmaj = -1; + let vmin = peer.versionMinor; + if ((typeof vmin === 'undefined')||(vmin === null)) vmin = -1; + let vrev = peer.versionRev; + if ((typeof vrev === 'undefined')||(vrev === null)) vrev = -1; + let lr = parseInt(bestPath.lastReceive)||0; + + let s = peerStatus[address]; + if ((!s)||(s[8] < lr)) { + peerStatus[address] = [ + ztAddress, + now, + vmaj, + vmin, + vrev, + id, + parseInt(a2[1])||0, + parseFloat(bestPath.linkQuality)||1.0, + lr, + a2[0] + ]; + } + ++count; + } + } + } + } + } + + console.log(name+': '+count+' peers with active direct paths.'); + return setTimeout(function() { doRootUpdate(name,id,ip,port,peersPath,authToken,interval); },interval); + }); +}; + +for(var r in roots) { + var rr = roots[r]; + if (rr.peers) + doRootUpdate(r,rr.id,rr.ip,rr.port,rr.peers,rr.authToken||null,config.interval||60000); +} + +return setTimeout(saveToDb,config.dbSaveInterval||60000); diff --git a/rule-compiler/README.md b/rule-compiler/README.md new file mode 100644 index 0000000..1ceeb71 --- /dev/null +++ b/rule-compiler/README.md @@ -0,0 +1,8 @@ +ZeroTier Rules Compiler +====== + +This script converts ZeroTier rules in human-readable format into rules suitable for import into a ZeroTier network controller. It's the script that is used in the rules editor on [ZeroTier Central](https://my.zerotier.com/). + +A command line interface is included that may be invoked as: `node cli.js `. + +See the [manual](https://www.zerotier.com/manual.shtml) for information about the rules engine and rules script syntax. diff --git a/rule-compiler/cli.js b/rule-compiler/cli.js new file mode 100644 index 0000000..75235ac --- /dev/null +++ b/rule-compiler/cli.js @@ -0,0 +1,47 @@ +'use strict'; + +var fs = require('fs'); +var RuleCompiler = require('./rule-compiler.js'); + +if (process.argv.length < 3) { + console.log('Usage: node cli.js '); + process.exit(1); +} + +var rules = []; +var caps = {}; +var tags = {}; +var err = RuleCompiler.compile(fs.readFileSync(process.argv[2]).toString(),rules,caps,tags); + +if (err) { + console.error('ERROR parsing '+process.argv[2]+' line '+err[0]+' column '+err[1]+': '+err[2]); + process.exit(1); +} else { + let capsArray = []; + let capabilitiesByName = {}; + for(let n in caps) { + capsArray.push(caps[n]); + capabilitiesByName[n] = caps[n].id; + } + let tagsArray = []; + for(let n in tags) { + let t = tags[n]; + let dfl = t['default']; + tagsArray.push({ + 'id': t.id, + 'default': (((dfl)||(dfl === 0)) ? dfl : null) + }); + } + + console.log(JSON.stringify({ + config: { + rules: rules, + capabilities: capsArray, + tags: tagsArray + }, + capabilitiesByName: capabilitiesByName, + tagsByName: tags + },null,1)); + + process.exit(0); +} diff --git a/rule-compiler/examples/capabilities-and-tags.ztrules b/rule-compiler/examples/capabilities-and-tags.ztrules new file mode 100644 index 0000000..9b35f28 --- /dev/null +++ b/rule-compiler/examples/capabilities-and-tags.ztrules @@ -0,0 +1,40 @@ +# This is a default rule set that allows IPv4 and IPv6 traffic. +# You can edit as needed. If your rule set gets large we recommend +# cutting and pasting it somewhere to keep a backup. + +# Drop all Ethernet frame types that are not IPv4 or IPv6 +drop + not ethertype 0x0800 # IPv4 + not ethertype 0x0806 # IPv4 ARP + not ethertype 0x86dd # IPv6 +; + +# Capability: outgoing SSH +cap ssh + id 1000 + accept + ipprotocol tcp + dport 22 + ; +; + +# A tag indicating which department people belong to +tag department + id 1000 + enum 100 sales + enum 200 marketing + enum 300 accounting + enum 400 engineering +; + +# Accept all traffic between members of the same department +accept + tdiff department 0 +; + +# You can insert other drop, tee, etc. rules here. This rule +# set ends with a blanket accept, making it permissive by +# default. + +accept; + diff --git a/rule-compiler/package.json b/rule-compiler/package.json new file mode 100644 index 0000000..1db006b --- /dev/null +++ b/rule-compiler/package.json @@ -0,0 +1,18 @@ +{ + "name": "zerotier-rule-compiler", + "version": "1.2.2-2", + "description": "ZeroTier Rule Script Compiler", + "main": "cli.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/zerotier/ZeroTierOne/rule-compiler" + }, + "keywords": [ + "ZeroTier" + ], + "author": "ZeroTier, Inc. ", + "license": "GPL-2.0" +} diff --git a/rule-compiler/rule-compiler.js b/rule-compiler/rule-compiler.js new file mode 100644 index 0000000..bd84824 --- /dev/null +++ b/rule-compiler/rule-compiler.js @@ -0,0 +1,904 @@ +'use strict'; + +// Names for bits in characteristics -- 0==LSB, 63==MSB +const CHARACTERISTIC_BITS = { + 'inbound': 63, + 'multicast': 62, + 'broadcast': 61, + 'ipauth': 60, + 'macauth': 59, + 'tcp_fin': 0, + 'tcp_syn': 1, + 'tcp_rst': 2, + 'tcp_psh': 3, + 'tcp_ack': 4, + 'tcp_urg': 5, + 'tcp_ece': 6, + 'tcp_cwr': 7, + 'tcp_ns': 8, + 'tcp_rs2': 9, + 'tcp_rs1': 10, + 'tcp_rs0': 11 +}; + +// Shorthand names for common ethernet types +const ETHERTYPES = { + 'ipv4': 0x0800, + 'arp': 0x0806, + 'wol': 0x0842, + 'rarp': 0x8035, + 'ipv6': 0x86dd, + 'atalk': 0x809b, + 'aarp': 0x80f3, + 'ipx_a': 0x8137, + 'ipx_b': 0x8138 +}; + +// Shorthand names for common IP protocols +const IP_PROTOCOLS = { + 'icmp': 0x01, + 'icmp4': 0x01, + 'icmpv4': 0x01, + 'igmp': 0x02, + 'ipip': 0x04, + 'tcp': 0x06, + 'egp': 0x08, + 'igp': 0x09, + 'udp': 0x11, + 'rdp': 0x1b, + 'esp': 0x32, + 'ah': 0x33, + 'icmp6': 0x3a, + 'icmpv6': 0x3a, + 'l2tp': 0x73, + 'sctp': 0x84, + 'udplite': 0x88 +}; + +// Keywords that open new blocks that must be terminated by a semicolon +const OPEN_BLOCK_KEYWORDS = { + 'macro': true, + 'tag': true, + 'cap': true, + 'drop': true, + 'accept': true, + 'tee': true, + 'watch': true, + 'redirect': true, + 'break': true +}; + +// Reserved words that can't be used as tag, capability, or rule set names +const RESERVED_WORDS = { + 'macro': true, + 'tag': true, + 'cap': true, + 'default': true, + + 'drop': true, + 'accept': true, + 'tee': true, + 'watch': true, + 'redirect': true, + 'break': true, + + 'ztsrc': true, + 'ztdest': true, + 'vlan': true, + 'vlanpcp': true, + 'vlandei': true, + 'ethertype': true, + 'macsrc': true, + 'macdest': true, + 'ipsrc': true, + 'ipdest': true, + 'iptos': true, + 'ipprotocol': true, + 'icmp': true, + 'sport': true, + 'dport': true, + 'chr': true, + 'framesize': true, + 'random': true, + 'tand': true, + 'tor': true, + 'txor': true, + 'tdiff': true, + 'teq': true, + 'tseq': true, + 'treq': true, + + 'type': true, + 'enum': true, + 'class': true, + 'define': true, + 'import': true, + 'include': true, + 'log': true, + 'not': true, + 'xor': true, + 'or': true, + 'and': true, + 'set': true, + 'var': true, + 'let': true +}; + +const KEYWORD_TO_API_MAP = { + 'drop': 'ACTION_DROP', + 'accept': 'ACTION_ACCEPT', + 'tee': 'ACTION_TEE', + 'watch': 'ACTION_WATCH', + 'redirect': 'ACTION_REDIRECT', + 'break': 'ACTION_BREAK', + + 'ztsrc': 'MATCH_SOURCE_ZEROTIER_ADDRESS', + 'ztdest': 'MATCH_DEST_ZEROTIER_ADDRESS', + 'vlan': 'MATCH_VLAN_ID', + 'vlanpcp': 'MATCH_VLAN_PCP', + 'vlandei': 'MATCH_VLAN_DEI', + 'ethertype': 'MATCH_ETHERTYPE', + 'macsrc': 'MATCH_MAC_SOURCE', + 'macdest': 'MATCH_MAC_DEST', + //'ipsrc': '', // special handling since we programmatically differentiate between V4 and V6 + //'ipdest': '', // special handling + 'iptos': 'MATCH_IP_TOS', + 'ipprotocol': 'MATCH_IP_PROTOCOL', + 'icmp': 'MATCH_ICMP', + 'sport': 'MATCH_IP_SOURCE_PORT_RANGE', + 'dport': 'MATCH_IP_DEST_PORT_RANGE', + 'chr': 'MATCH_CHARACTERISTICS', + 'framesize': 'MATCH_FRAME_SIZE_RANGE', + 'random': 'MATCH_RANDOM', + 'tand': 'MATCH_TAGS_BITWISE_AND', + 'tor': 'MATCH_TAGS_BITWISE_OR', + 'txor': 'MATCH_TAGS_BITWISE_XOR', + 'tdiff': 'MATCH_TAGS_DIFFERENCE', + 'teq': 'MATCH_TAGS_EQUAL', + 'tseq': 'MATCH_TAG_SENDER', + 'treq': 'MATCH_TAG_RECEIVER' +}; + +// Number of args for each match +const MATCH_ARG_COUNTS = { + 'ztsrc': 1, + 'ztdest': 1, + 'vlan': 1, + 'vlanpcp': 1, + 'vlandei': 1, + 'ethertype': 1, + 'macsrc': 1, + 'macdest': 1, + 'ipsrc': 1, + 'ipdest': 1, + 'iptos': 2, + 'ipprotocol': 1, + 'icmp': 2, + 'sport': 1, + 'dport': 1, + 'chr': 1, + 'framesize': 1, + 'random': 1, + 'tand': 2, + 'tor': 2, + 'txor': 2, + 'tdiff': 2, + 'teq': 2, + 'tseq': 2, + 'treq': 2 +}; + +// Regex of all alphanumeric characters in Unicode +const INTL_ALPHANUM_REGEX = new RegExp('[0-9A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]'); + +// Checks whether something is a valid capability, tag, or macro name +function _isValidName(n) +{ + if ((typeof n !== 'string')||(n.length === 0)) return false; + if ("0123456789".indexOf(n.charAt(0)) >= 0) return false; + for(let i=0;i 2)&&(n.substr(0,2) === '0x')) + n = parseInt(n.substr(2),16); + else n = parseInt(n,10); + return (((typeof n === 'number')&&(n !== null)&&(!isNaN(n))) ? n : -1); + } catch (e) { + return -1; + } +} + +function _cleanMac(m) +{ + m = m.toLowerCase(); + var m2 = ''; + for(let i=0;((i= 0) { + m2 += c; + if ((m2.length > 0)&&(m2.length !== 17)&&((m2.length & 1) === 0)) + m2 += ':'; + } + } + return m2; +} + +function _cleanHex(m) +{ + m = m.toLowerCase(); + var m2 = ''; + for(let i=0;i= 0) + m2 += c; + } + return m2; +} + +function _renderMatches(mtree,rules,macros,caps,tags,params) +{ + let not = false; + let or = false; + for(let k=0;k= mtree.length) + return [ mtree[k - 1][1],mtree[k - 1][2],'Missing argument(s) to match.' ]; + let arg = mtree[k][0]; + if ((typeof arg !== 'string')||(arg in RESERVED_WORDS)||(arg.length === 0)) + return [ mtree[k - 1][1],mtree[k - 1][2],'Missing argument(s) to match (invalid argument or argument is reserved word).' ]; + if (arg.charAt(0) === '$') { + let tmp = params[arg]; + if (typeof tmp === 'undefined') + return [ mtree[k][1],mtree[k][2],'Undefined variable name.' ]; + args.push([ tmp,mtree[k][1],mtree[k][2] ]); + } else { + args.push(mtree[k]); + } + } + + switch(match) { + case 'ztsrc': + case 'ztdest': { + let zt = _cleanHex(args[0][0]); + if (zt.length !== 10) + return [ args[0][1],args[0][2],'Invalid ZeroTier address.' ]; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'zt': zt + }); + } break; + + case 'vlan': + case 'vlanpcp': + case 'vlandei': + case 'ethertype': + case 'ipprotocol': { + let num = null; + switch (match) { + case 'ethertype': num = ETHERTYPES[args[0][0]]; break; + case 'ipprotocol': num = IP_PROTOCOLS[args[0][0]]; break; + } + if (typeof num !== 'number') + num = _parseNum(args[0][0]); + if ((typeof num !== 'number')||(num < 0)||(num > 0xffffffff)||(num === null)) + return [ args[0][1],args[0][2],'Invalid numeric value.' ]; + let r = { + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or + }; + switch(match) { + case 'vlan': r['vlanId'] = num; break; + case 'vlanpcp': r['vlanPcp'] = num; break; + case 'vlandei': r['vlanDei'] = num; break; + case 'ethertype': r['etherType'] = num; break; + case 'ipprotocol': r['ipProtocol'] = num; break; + } + rules.push(r); + } break; + + case 'random': { + let num = parseFloat(args[0][0])||0.0; + if (num < 0.0) num = 0.0; + if (num > 1.0) num = 1.0; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'probability': Math.floor(4294967295 * num) + }); + } break; + + case 'macsrc': + case 'macdest': { + let mac = _cleanMac(args[0][0]); + if (mac.length !== 17) + return [ args[0][1],args[0][2],'Invalid MAC address.' ]; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'mac': mac + }); + } break; + + case 'ipsrc': + case 'ipdest': { + let ip = args[0][0]; + let slashIdx = ip.indexOf('/'); + if (slashIdx <= 0) + return [ args[0][1],args[0][2],'Missing /bits netmask length designation in IP.' ]; + let ipOnly = ip.substr(0,slashIdx); + if (IPV6_REGEX.test(ipOnly)) { + rules.push({ + 'type': ((match === 'ipsrc') ? 'MATCH_IPV6_SOURCE' : 'MATCH_IPV6_DEST'), + 'not': not, + 'or': or, + 'ip': ip + }); + } else if (IPV4_REGEX.test(ipOnly)) { + rules.push({ + 'type': ((match === 'ipsrc') ? 'MATCH_IPV4_SOURCE' : 'MATCH_IPV4_DEST'), + 'not': not, + 'or': or, + 'ip': ip + }); + } else { + return [ args[0][1],args[0][2],'Invalid IP address (not valid IPv4 or IPv6).' ]; + } + } break; + + case 'icmp': { + let icmpType = _parseNum(args[0][0]); + if ((icmpType < 0)||(icmpType > 0xff)) + return [ args[0][1],args[0][2],'Missing or invalid ICMP type.' ]; + let icmpCode = _parseNum(args[1][0]); // -1 okay, indicates don't match code + if (icmpCode > 0xff) + return [ args[1][1],args[1][2],'Invalid ICMP code (use -1 for none).' ]; + rules.push({ + 'type': 'MATCH_ICMP', + 'not': not, + 'or': or, + 'icmpType': icmpType, + 'icmpCode': ((icmpCode < 0) ? null : icmpCode) + }); + } break; + + case 'sport': + case 'dport': + case 'framesize': { + let arg = args[0][0]; + let fn = null; + let tn = null; + if (arg.indexOf('-') > 0) { + let asplit = arg.split('-'); + if (asplit.length !== 2) { + return [ args[0][1],args[0][2],'Invalid numeric range.' ]; + } else { + fn = _parseNum(asplit[0]); + tn = _parseNum(asplit[1]); + } + } else { + fn = _parseNum(arg); + tn = fn; + } + if ((fn < 0)||(fn > 0xffff)||(tn < 0)||(tn > 0xffff)||(tn < fn)) + return [ args[0][1],args[0][2],'Invalid numeric range.' ]; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'start': fn, + 'end': tn + }); + } break; + + case 'iptos': { + let mask = _parseNum(args[0][0]); + if ((typeof mask !== 'number')||(mask < 0)||(mask > 0xff)||(mask === null)) + return [ args[0][1],args[0][2],'Invalid mask.' ]; + let arg = args[1][0]; + let fn = null; + let tn = null; + if (arg.indexOf('-') > 0) { + let asplit = arg.split('-'); + if (asplit.length !== 2) { + return [ args[1][1],args[1][2],'Invalid value range.' ]; + } else { + fn = _parseNum(asplit[0]); + tn = _parseNum(asplit[1]); + } + } else { + fn = _parseNum(arg); + tn = fn; + } + if ((fn < 0)||(fn > 0xff)||(tn < 0)||(tn > 0xff)||(tn < fn)) + return [ args[1][1],args[1][2],'Invalid value range.' ]; + rules.push({ + 'type': 'MATCH_IP_TOS', + 'not': not, + 'or': or, + 'mask': mask, + 'start': fn, + 'end': tn + }); + } break; + + case 'chr': { + let chrb = args[0][0].split(/[,]+/); + let maskhi = 0; + let masklo = 0; + for(let i=0;i 0) { + let tmp = CHARACTERISTIC_BITS[chrb[i]]; + let bit = (typeof tmp === 'number') ? tmp : _parseNum(chrb[i]); + if ((bit < 0)||(bit > 63)) + return [ args[0][1],args[0][2],'Invalid bit index (range 0-63) or unrecognized name.' ]; + if (bit >= 32) + maskhi |= Math.abs(1 << (bit - 32)); + else masklo |= Math.abs(1 << bit); + } + } + maskhi = Math.abs(maskhi).toString(16); + while (maskhi.length < 8) maskhi = '0' + maskhi; + masklo = Math.abs(masklo).toString(16); + while (masklo.length < 8) masklo = '0' + masklo; + rules.push({ + 'type': 'MATCH_CHARACTERISTICS', + 'not': not, + 'or': or, + 'mask': (maskhi + masklo) + }); + } break; + + case 'tand': + case 'tor': + case 'txor': + case 'tdiff': + case 'teq': + case 'tseq': + case 'treq': { + let tag = tags[args[0][0]]; + let tagId = -1; + let tagValue = -1; + if (tag) { + tagId = tag.id; + tagValue = args[1][0]; + if (tagValue in tag.flags) + tagValue = tag.flags[tagValue]; + else if (tagValue in tag.enums) + tagValue = tag.enums[tagValue]; + else tagValue = _parseNum(tagValue); + } else { + tagId = _parseNum(args[0][0]); + tagValue = _parseNum(args[1][0]); + } + if ((tagId < 0)||(tagId > 0xffffffff)) + return [ args[0][1],args[0][2],'Undefined tag name and invalid tag value.' ]; + if ((tagValue < 0)||(tagValue > 0xffffffff)) + return [ args[1][1],args[1][2],'Invalid tag value or unrecognized flag/enum name.' ]; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'id': tagId, + 'value': tagValue + }); + } break; + } + + not = false; + or = false; + } + } + return null; +} + +function _renderActions(rtree,rules,macros,caps,tags,params) +{ + for(let k=0;k= rtree.length) + return [ rtree[k][1],rtree[k][2],'Include directive is missing a macro name.' ]; + let macroName = rtree[k + 1][0]; + ++k; + + let macroParamArray = []; + let parenIdx = macroName.indexOf('('); + if (parenIdx > 0) { + let pns = macroName.substr(parenIdx + 1).split(/[,)]+/); + for(let k=0;k 0) + macroParamArray.push(pns[k]); + } + macroName = macroName.substr(0,parenIdx); + } + + let macro = macros[macroName]; + if (!macro) + return [ rtree[k][1],rtree[k][2],'Macro name not found.' ]; + let macroParams = {}; + for(let param in macro.params) { + let pidx = macro.params[param]; + if (pidx >= macroParamArray.length) + return [ rtree[k][1],rtree[k][2],'Missing one or more required macro parameter.' ]; + macroParams[param] = macroParamArray[pidx]; + } + + let err = _renderActions(macro.rules,rules,macros,caps,tags,macroParams); + if (err !== null) + return err; + } else if ((action === 'drop')||(action === 'accept')||(action === 'break')) { // actions without arguments + if (((k + 1) < rtree.length)&&(Array.isArray(rtree[k + 1][0]))) { + let mtree = rtree[k + 1]; ++k; + let err = _renderMatches(mtree,rules,macros,caps,tags,params); + if (err !== null) + return err; + } + rules.push({ + 'type': KEYWORD_TO_API_MAP[action] + }); + } else if ((action === 'tee')||(action === 'watch')) { // actions with arguments (ZeroTier address) + if (((k + 1) < rtree.length)&&(Array.isArray(rtree[k + 1][0]))&&(rtree[k + 1][0].length >= 2)) { + let mtree = rtree[k + 1]; ++k; + let maxLength = _parseNum(mtree[0][0]); + if ((maxLength < -1)||(maxLength > 0xffff)) + return [ mtree[0][1],mtree[1][2],'Tee/watch max packet length to forward invalid or out of range.' ]; + let target = mtree[1][0]; + if ((typeof target !== 'string')||(target.length !== 10)) + return [ mtree[1][1],mtree[1][2],'Missing or invalid ZeroTier address target for tee/watch.' ]; + let err = _renderMatches(mtree.slice(2),rules,macros,caps,tags,params); + if (err !== null) + return err; + rules.push({ + 'type': KEYWORD_TO_API_MAP[action], + 'address': target, + 'length': maxLength + }); + } else { + return [ rtree[k][1],rtree[k][2],'The tee and watch actions require two paremters (max length or 0 for all, target).' ]; + } + } else if (action === 'redirect') { + if (((k + 1) < rtree.length)&&(Array.isArray(rtree[k + 1][0]))&&(rtree[k + 1][0].length >= 1)) { + let mtree = rtree[k + 1]; ++k; + let target = mtree[0][0]; + if ((typeof target !== 'string')||(target.length !== 10)) + return [ mtree[0][1],mtree[0][2],'Missing or invalid ZeroTier address target for redirect.' ]; + let err = _renderMatches(mtree.slice(1),rules,macros,caps,tags,params); + if (err !== null) + return err; + rules.push({ + 'type': KEYWORD_TO_API_MAP[action], + 'address': target + }); + } else { + return [ rtree[k][1],rtree[k][2],'The redirect action requires a target parameter.' ]; + } + } else { + return [ rtree[k][1],rtree[k][2],'Unrecognized action or directive in rule set.' ]; + } + } + + return null; +} + +function compile(src,rules,caps,tags) +{ + try { + if (typeof src !== 'string') + return [ 0,0,'"src" parameter must be a string.' ]; + + // Pass 1: parse source into a tree of arrays of elements. Each element is a 3-item + // tuple consisting of string, line number, and character index in line to enable + // informative error messages to be returned. + + var blockStack = [ [] ]; + var curr = [ '',-1,-1 ]; + var skipRestOfLine = false; + for(let idx=0,lineNo=1,lineIdx=0;idx 0) { + let endOfBlock = false; + if (curr[0].charAt(curr[0].length - 1) === ';') { + endOfBlock = true; + curr[0] = curr[0].substr(0,curr[0].length - 1); + } + + if (curr[0].length > 0) { + blockStack[blockStack.length - 1].push(curr); + } + if ((endOfBlock)&&(blockStack.length > 1)&&(blockStack[blockStack.length - 1].length > 0)) { + blockStack[blockStack.length - 2].push(blockStack[blockStack.length - 1]); + blockStack.pop(); + } else if (curr[0] in OPEN_BLOCK_KEYWORDS) { + blockStack.push([]); + } + + curr = [ '',-1,-1 ]; + } + break; + default: + if (curr[0].length === 0) { + if (ch === '#') { + skipRestOfLine = true; + continue; + } else { + curr[1] = lineNo; + curr[2] = lineIdx; + } + } + curr[0] += ch; + break; + } + } + } + + if (curr[0].length > 0) { + if (curr[0].charAt(curr[0].length - 1) === ';') + curr[0] = curr[0].substr(0,curr[0].length - 1); + if (curr[0].length > 0) + blockStack[blockStack.length - 1].push(curr); + } + while ((blockStack.length > 1)&&(blockStack[blockStack.length - 1].length > 0)) { + blockStack[blockStack.length - 2].push(blockStack[blockStack.length - 1]); + blockStack.pop(); + } + var parsed = blockStack[0]; + + // Pass 2: parse tree into capabilities, tags, rule sets, and document-level rules. + + let baseRuleTree = []; + let macros = {}; + for(let i=0;i= parsed.length) || (!Array.isArray(parsed[i + 1])) || (parsed[i + 1].length < 1) || (!Array.isArray(parsed[i + 1][0])) ) + return [ parsed[i][1],parsed[i][2],'Macro definition is missing name.' ]; + let macro = parsed[++i]; + let macroName = macro[0][0].toLowerCase(); + + let params = {}; + let parenIdx = macroName.indexOf('('); + if (parenIdx > 0) { + let pns = macroName.substr(parenIdx + 1).split(/[,)]+/); + for(let k=0;k 0) + params[pns[k]] = k; + } + macroName = macroName.substr(0,parenIdx); + } + + if (!_isValidName(macroName)) + return [ macro[0][1],macro[0][2],'Invalid macro name.' ]; + if (macroName in RESERVED_WORDS) + return [ macro[0][1],macro[0][2],'Macro name is a reserved word.' ]; + + if (macroName in macros) + return [ macro[0][1],macro[0][2],'Multiple definition of macro name.' ]; + + macros[macroName] = { + params: params, + rules: macro.slice(1) + }; + } else if (keyword === 'tag') { + // Define tags + + if ( ((i + 1) >= parsed.length) || (!Array.isArray(parsed[i + 1])) || (parsed[i + 1].length < 1) || (!Array.isArray(parsed[i + 1][0])) ) + return [ parsed[i][1],parsed[i][2],'Tag definition is missing name.' ]; + let tag = parsed[++i]; + let tagName = tag[0][0].toLowerCase(); + + if (!_isValidName(tagName)) + return [ tag[0][1],tag[0][2],'Invalid tag name.' ]; + if (tagName in RESERVED_WORDS) + return [ tag[0][1],tag[0][2],'Tag name is a reserved word.' ]; + + if (tagName in tags) + return [ tag[0][1],tag[0][2],'Multiple definition of tag name.' ]; + + let flags = {}; + let enums = {}; + let id = -1; + let dfl = null; + for(let k=1;k= 0) + return [ tag[k][1],tag[k][2],'Duplicate tag id definition.' ]; + if ((k + 1) >= tag.length) + return [ tag[k][1],tag[k][2],'Missing numeric value for ID.' ]; + id = _parseNum(tag[++k][0]); + if ((id < 0)||(id > 0xffffffff)) + return [ tag[k][1],tag[k][2],'Invalid or out of range tag ID.' ]; + } else if (tkeyword === 'default') { + if (dfl !== null) + return [ tag[k][1],tag[k][2],'Duplicate tag default directive.' ]; + if ((k + 1) >= tag.length) + return [ tag[k][1],tag[k][2],'Missing value for default.' ]; + dfl = tag[++k][0]; + } else if (tkeyword === 'flag') { + if ((k + 2) >= tag.length) + return [ tag[k][1],tag[k][2],'Missing tag flag name or bit index.' ]; + ++k; + let bits = tag[k][0].split(/[,]+/); + let mask = 0; + for(let j=0;j 31)) + return [ tag[k][1],tag[k][2],'Bit index invalid, out of range, or references an undefined flag name.' ]; + mask |= (1 << b); + } + } + let flagName = tag[++k][0].toLowerCase(); + if (!_isValidName(flagName)) + return [ tag[k][1],tag[k][2],'Invalid or reserved flag name.' ]; + if (flagName in flags) + return [ tag[k][1],tag[k][2],'Duplicate flag name in tag definition.' ]; + flags[flagName] = mask; + } else if (tkeyword === 'enum') { + if ((k + 2) >= tag.length) + return [ tag[k][1],tag[k][2],'Missing tag enum name or value.' ]; + ++k; + let value = _parseNum(tag[k][0]); + if ((value < 0)||(value > 0xffffffff)) + return [ tag[k][1],tag[k][2],'Tag enum value invalid or out of range.' ]; + let enumName = tag[++k][0].toLowerCase(); + if (!_isValidName(enumName)) + return [ tag[k][1],tag[k][2],'Invalid or reserved tag enum name.' ]; + if (enumName in enums) + return [ tag[k][1],tag[k][2],'Duplicate enum name in tag definition.' ]; + enums[enumName] = value; + } else { + return [ tag[k][1],tag[k][2],'Unrecognized keyword in tag definition.' ]; + } + } + if (id < 0) + return [ tag[0][1],tag[0][2],'Tag definition is missing a numeric ID.' ]; + + if (typeof dfl === 'string') { + let dfl2 = enums[dfl]; + if (typeof dfl2 === 'number') { + dfl = dfl2; + } else { + dfl2 = flags[dfl]; + if (typeof dfl2 === 'number') { + dfl = dfl2; + } else { + dfl = Math.abs(parseInt(dfl)||0) & 0xffffffff; + } + } + } else if (typeof dfl === 'number') { + dfl = Math.abs(dfl) & 0xffffffff; + } + + tags[tagName] = { + 'id': id, + 'default': dfl, + 'enums': enums, + 'flags': flags + }; + } else if (keyword === 'cap') { + // Define capabilities + + if ( ((i + 1) >= parsed.length) || (!Array.isArray(parsed[i + 1])) || (parsed[i + 1].length < 1) || (!Array.isArray(parsed[i + 1][0])) ) + return [ parsed[i][1],parsed[i][2],'Capability definition is missing name.' ]; + let cap = parsed[++i]; + let capName = cap[0][0].toLowerCase(); + + if (!_isValidName(capName)) + return [ cap[0][1],cap[0][2],'Invalid capability name.' ]; + if (capName in RESERVED_WORDS) + return [ cap[0][1],cap[0][2],'Capability name is a reserved word.' ]; + + if (capName in caps) + return [ cap[0][1],cap[0][2],'Multiple definition of capability name.' ]; + + let capRules = []; + let id = -1; + let dfl = false; + for(let k=1;k= 0) + return [ cap[k][1],cap[k][2],'Duplicate id directive in capability definition.' ]; + if ((k + 1) >= cap.length) + return [ cap[k][1],cap[k][2],'Missing value for ID.' ]; + id = _parseNum(cap[++k][0]); + if ((id < 0)||(id > 0xffffffff)) + return [ cap[k - 1][1],cap[k - 1][2],'Invalid or out of range capability ID.' ]; + for(let cn in caps) { + if (caps[cn].id === id) + return [ cap[k - 1][1],cap[k - 1][2],'Duplicate capability ID.' ]; + } + } else if (dn === 'default') { + dfl = true; + } else { + capRules.push(cap[k]); + } + } + if (id < 0) + return [ cap[0][1],cap[0][2],'Capability definition is missing a numeric ID.' ]; + + caps[capName] = { + 'id': id, + 'default': dfl, + 'rules': capRules + }; + } else { + baseRuleTree.push(parsed[i]); + } + } + + // Pass 3: render low-level ZeroTier rules arrays for capabilities and base. + + for(let capName in caps) { + let r = []; + let err = _renderActions(caps[capName].rules,r,macros,caps,tags,{}); + if (err !== null) + return err; + caps[capName].rules = r; + } + + let err = _renderActions(baseRuleTree,rules,macros,caps,tags,{}); + if (err !== null) + return err; + + return null; + } catch (e) { + console.log(e.stack); + return [ 0,0,'Unexpected exception: '+e.toString() ]; + } +} + +exports.compile = compile; diff --git a/selftest.cpp b/selftest.cpp new file mode 100644 index 0000000..91d304a --- /dev/null +++ b/selftest.cpp @@ -0,0 +1,1144 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "node/Constants.hpp" +#include "node/Hashtable.hpp" +#include "node/RuntimeEnvironment.hpp" +#include "node/InetAddress.hpp" +#include "node/Utils.hpp" +#include "node/Identity.hpp" +#include "node/Buffer.hpp" +#include "node/Packet.hpp" +#include "node/Salsa20.hpp" +#include "node/MAC.hpp" +#include "node/NetworkConfig.hpp" +#include "node/Peer.hpp" +#include "node/Dictionary.hpp" +#include "node/SHA512.hpp" +#include "node/C25519.hpp" +#include "node/Poly1305.hpp" +#include "node/CertificateOfMembership.hpp" +#include "node/Node.hpp" +#include "node/IncomingPacket.hpp" + +#include "osdep/OSUtils.hpp" +#include "osdep/Phy.hpp" +#include "osdep/Http.hpp" +#include "osdep/PortMapper.hpp" +#include "osdep/Thread.hpp" + +#include "controller/JSONDB.hpp" + +#ifdef ZT_USE_X64_ASM_SALSA2012 +#include "ext/x64-salsa2012-asm/salsa2012.h" +#endif +#ifdef ZT_USE_ARM32_NEON_ASM_SALSA2012 +#include "ext/arm32-neon-salsa2012-asm/salsa2012.h" +#endif + +#ifdef __WINDOWS__ +#include +#endif + +using namespace ZeroTier; + +////////////////////////////////////////////////////////////////////////////// + +#define KNOWN_GOOD_IDENTITY "8e4df28b72:0:ac3d46abe0c21f3cfe7a6c8d6a85cfcffcb82fbd55af6a4d6350657c68200843fa2e16f9418bbd9702cae365f2af5fb4c420908b803a681d4daef6114d78a2d7:bd8dd6e4ce7022d2f812797a80c6ee8ad180dc4ebf301dec8b06d1be08832bddd63a2f1cfa7b2c504474c75bdc8898ba476ef92e8e2d0509f8441985171ff16e" +#define KNOWN_BAD_IDENTITY "9e4df28b72:0:ac3d46abe0c21f3cfe7a6c8d6a85cfcffcb82fbd55af6a4d6350657c68200843fa2e16f9418bbd9702cae365f2af5fb4c420908b803a681d4daef6114d78a2d7:bd8dd6e4ce7022d2f812797a80c6ee8ad180dc4ebf301dec8b06d1be08832bddd63a2f1cfa7b2c504474c75bdc8898ba476ef92e8e2d0509f8441985171ff16e" + +static const unsigned char s20TV0Key[32] = { 0x0f,0x62,0xb5,0x08,0x5b,0xae,0x01,0x54,0xa7,0xfa,0x4d,0xa0,0xf3,0x46,0x99,0xec,0x3f,0x92,0xe5,0x38,0x8b,0xde,0x31,0x84,0xd7,0x2a,0x7d,0xd0,0x23,0x76,0xc9,0x1c }; +static const unsigned char s20TV0Iv[8] = { 0x28,0x8f,0xf6,0x5d,0xc4,0x2b,0x92,0xf9 }; +static const unsigned char s20TV0Ks[64] = { 0x5e,0x5e,0x71,0xf9,0x01,0x99,0x34,0x03,0x04,0xab,0xb2,0x2a,0x37,0xb6,0x62,0x5b,0xf8,0x83,0xfb,0x89,0xce,0x3b,0x21,0xf5,0x4a,0x10,0xb8,0x10,0x66,0xef,0x87,0xda,0x30,0xb7,0x76,0x99,0xaa,0x73,0x79,0xda,0x59,0x5c,0x77,0xdd,0x59,0x54,0x2d,0xa2,0x08,0xe5,0x95,0x4f,0x89,0xe4,0x0e,0xb7,0xaa,0x80,0xa8,0x4a,0x61,0x76,0x66,0x3f }; + +static const unsigned char s2012TV0Key[32] = { 0x0f,0x62,0xb5,0x08,0x5b,0xae,0x01,0x54,0xa7,0xfa,0x4d,0xa0,0xf3,0x46,0x99,0xec,0x3f,0x92,0xe5,0x38,0x8b,0xde,0x31,0x84,0xd7,0x2a,0x7d,0xd0,0x23,0x76,0xc9,0x1c }; +static const unsigned char s2012TV0Iv[8] = { 0x28,0x8f,0xf6,0x5d,0xc4,0x2b,0x92,0xf9 }; +static const unsigned char s2012TV0Ks[64] = { 0x99,0xDB,0x33,0xAD,0x11,0xCE,0x0C,0xCB,0x3B,0xFD,0xBF,0x8D,0x0C,0x18,0x16,0x04,0x52,0xD0,0x14,0xCD,0xE9,0x89,0xB4,0xC4,0x11,0xA5,0x59,0xFF,0x7C,0x20,0xA1,0x69,0xE6,0xDC,0x99,0x09,0xD8,0x16,0xBE,0xCE,0xDC,0x40,0x63,0xCE,0x07,0xCE,0xA8,0x28,0xF4,0x4B,0xF9,0xB6,0xC9,0xA0,0xA0,0xB2,0x00,0xE1,0xB5,0x2A,0xF4,0x18,0x59,0xC5 }; + +static const unsigned char poly1305TV0Input[32] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; +static const unsigned char poly1305TV0Key[32] = { 0x74,0x68,0x69,0x73,0x20,0x69,0x73,0x20,0x33,0x32,0x2d,0x62,0x79,0x74,0x65,0x20,0x6b,0x65,0x79,0x20,0x66,0x6f,0x72,0x20,0x50,0x6f,0x6c,0x79,0x31,0x33,0x30,0x35 }; +static const unsigned char poly1305TV0Tag[16] = { 0x49,0xec,0x78,0x09,0x0e,0x48,0x1e,0xc6,0xc2,0x6b,0x33,0xb9,0x1c,0xcc,0x03,0x07 }; + +static const unsigned char poly1305TV1Input[12] = { 0x48,0x65,0x6c,0x6c,0x6f,0x20,0x77,0x6f,0x72,0x6c,0x64,0x21 }; +static const unsigned char poly1305TV1Key[32] = { 0x74,0x68,0x69,0x73,0x20,0x69,0x73,0x20,0x33,0x32,0x2d,0x62,0x79,0x74,0x65,0x20,0x6b,0x65,0x79,0x20,0x66,0x6f,0x72,0x20,0x50,0x6f,0x6c,0x79,0x31,0x33,0x30,0x35 }; +static const unsigned char poly1305TV1Tag[16] = { 0xa6,0xf7,0x45,0x00,0x8f,0x81,0xc9,0x16,0xa2,0x0d,0xcc,0x74,0xee,0xf2,0xb2,0xf0 }; + +static const char *sha512TV0Input = "supercalifragilisticexpealidocious"; +static const unsigned char sha512TV0Digest[64] = { 0x18,0x2a,0x85,0x59,0x69,0xe5,0xd3,0xe6,0xcb,0xf6,0x05,0x24,0xad,0xf2,0x88,0xd1,0xbb,0xf2,0x52,0x92,0x81,0x24,0x31,0xf6,0xd2,0x52,0xf1,0xdb,0xc1,0xcb,0x44,0xdf,0x21,0x57,0x3d,0xe1,0xb0,0x6b,0x68,0x75,0x95,0x9f,0x3b,0x6f,0x87,0xb1,0x13,0x81,0xd0,0xbc,0x79,0x2c,0x43,0x3a,0x13,0x55,0x3c,0xe0,0x84,0xc2,0x92,0x55,0x31,0x1c }; + +struct C25519TestVector +{ + unsigned char pub1[64]; + unsigned char priv1[64]; + unsigned char pub2[64]; + unsigned char priv2[64]; + unsigned char agreement[64]; + unsigned char agreementSignedBy1[96]; + unsigned char agreementSignedBy2[96]; +}; + +#define ZT_NUM_C25519_TEST_VECTORS 32 + +static const C25519TestVector C25519_TEST_VECTORS[ZT_NUM_C25519_TEST_VECTORS] = { + {{0xa1,0xfc,0x7a,0xb4,0x6d,0xdf,0x7d,0xcf,0xe7,0xec,0x75,0xe5,0xfa,0xdd,0x11,0xcb,0xcc,0x37,0xf8,0x84,0x5d,0x1c,0x92,0x4e,0x09,0x89,0x65,0xfc,0xd8,0xe9,0x5a,0x30,0xda,0xe4,0x86,0xa3,0x35,0xb4,0x19,0x0c,0xbc,0x7b,0xcb,0x3e,0xb9,0x4c,0xbd,0x16,0xe8,0x3d,0x13,0x2b,0xc9,0xc3,0x39,0xea,0xf1,0x42,0xe7,0x6f,0x69,0x78,0x9a,0xb7},{0xe5,0xf3,0x7b,0xd4,0x0e,0xc9,0xdc,0x77,0x50,0x86,0xdc,0xf4,0x2e,0xbc,0xdb,0x27,0xf0,0x73,0xd4,0x58,0x73,0xc4,0x4b,0x71,0x8b,0x3c,0xc5,0x4f,0xa8,0x7c,0xa4,0x84,0xd9,0x96,0x23,0x73,0xb4,0x03,0x16,0xbf,0x1e,0xa1,0x2d,0xd8,0xc4,0x8a,0xe7,0x82,0x10,0xda,0xc9,0xe5,0x45,0x9b,0x01,0xdc,0x73,0xa6,0xc9,0x17,0xa8,0x15,0x31,0x6d},{0x3e,0x49,0xa4,0x0e,0x3a,0xaf,0xa3,0x07,0x3d,0xf7,0x2a,0xec,0x43,0xb1,0xd4,0x09,0x1a,0xcb,0x8e,0x92,0xf9,0x65,0x95,0x04,0x6d,0x2d,0x9b,0x34,0xa3,0xbf,0x51,0x00,0xe2,0xee,0x23,0xf5,0x28,0x0a,0xa9,0xb1,0x57,0x0b,0x96,0x56,0x62,0xba,0x12,0x94,0xaf,0xc6,0x5f,0xb5,0x61,0x43,0x0f,0xde,0x0b,0xab,0xfa,0x4f,0xfe,0xc5,0xe7,0x18},{0x00,0x4d,0x41,0x8d,0xe4,0x69,0x23,0xae,0x98,0xc4,0x3e,0x77,0x0f,0x1d,0x94,0x5d,0x29,0x3e,0x94,0x5a,0x38,0x39,0x20,0x0f,0xd3,0x6f,0x76,0xa2,0x29,0x02,0x03,0xcb,0x0b,0x7f,0x4f,0x1a,0x29,0x51,0x13,0x33,0x7c,0x99,0xb3,0x81,0x82,0x39,0x44,0x05,0x97,0xfb,0x0d,0xf2,0x93,0xa2,0x40,0x94,0xf4,0xff,0x5d,0x09,0x61,0xe4,0x5f,0x76},{0xab,0xce,0xd2,0x24,0xe8,0x93,0xb0,0xe7,0x72,0x14,0xdc,0xbb,0x7d,0x0f,0xd8,0x94,0x16,0x9e,0xb5,0x7f,0xd7,0x19,0x5f,0x3e,0x2d,0x45,0xd5,0xf7,0x90,0x0b,0x3e,0x05,0x18,0x2e,0x2b,0xf4,0xfa,0xd4,0xec,0x62,0x4a,0x4f,0x48,0x50,0xaf,0x1c,0xe8,0x9f,0x1a,0xe1,0x3d,0x70,0x49,0x00,0xa7,0xe3,0x5b,0x1e,0xa1,0x9b,0x68,0x1e,0xa1,0x73},{0xed,0xb6,0xd0,0xf0,0x06,0x6e,0x33,0x9c,0x86,0xfb,0xe8,0xc3,0x6c,0x8d,0xde,0xdd,0xa6,0xa0,0x2d,0xb9,0x07,0x29,0xa3,0x13,0xbb,0xa4,0xba,0xec,0x48,0xc8,0xf4,0x56,0x82,0x79,0xe2,0xb1,0xd3,0x3d,0x83,0x9f,0x10,0xe8,0x52,0xe6,0x8b,0x1c,0x33,0x9e,0x2b,0xd2,0xdb,0x62,0x1c,0x56,0xfd,0x50,0x40,0x77,0x81,0xab,0x21,0x67,0x3e,0x09,0x4f,0xf2,0x51,0xac,0x7d,0xe7,0xd1,0x5d,0x4b,0xe2,0x08,0xc6,0x3f,0x6a,0x4d,0xc8,0x5d,0x74,0xf6,0x3b,0xec,0x8e,0xc6,0x0c,0x32,0x27,0x2f,0x9c,0x09,0x48,0x59,0x10},{0x23,0x0f,0xa3,0xe2,0x69,0xce,0xb9,0xb9,0xd1,0x1c,0x4e,0xab,0x63,0xc9,0x2e,0x1e,0x7e,0xa2,0xa2,0xa0,0x49,0x2e,0x78,0xe4,0x8a,0x02,0x3b,0xa7,0xab,0x1f,0xd4,0xce,0x05,0xe2,0x80,0x09,0x09,0x3c,0x61,0xc7,0x10,0x3a,0x9c,0xf4,0x95,0xac,0x89,0x6f,0x23,0xb3,0x09,0xe2,0x24,0x3f,0xf6,0x96,0x02,0x36,0x41,0x16,0x32,0xe1,0x66,0x05,0x4f,0xf2,0x51,0xac,0x7d,0xe7,0xd1,0x5d,0x4b,0xe2,0x08,0xc6,0x3f,0x6a,0x4d,0xc8,0x5d,0x74,0xf6,0x3b,0xec,0x8e,0xc6,0x0c,0x32,0x27,0x2f,0x9c,0x09,0x48,0x59,0x10}}, + {{0xfd,0x81,0x14,0xf1,0x67,0x07,0x44,0xbb,0x93,0x84,0xa2,0xdc,0x36,0xdc,0xcc,0xb3,0x9e,0x82,0xd4,0x8b,0x42,0x56,0xfb,0xf2,0x6e,0x83,0x3b,0x16,0x2c,0x29,0xfb,0x39,0x29,0x48,0x85,0xe3,0xe3,0xf7,0xe7,0x80,0x49,0xd3,0x01,0x30,0x5a,0x2c,0x3f,0x4c,0xea,0x13,0xeb,0xda,0xf4,0x56,0x75,0x8d,0x50,0x1e,0x19,0x2d,0x29,0x2b,0xfb,0xdb},{0x85,0x34,0x4d,0xf7,0x39,0xbf,0x98,0x79,0x8c,0x98,0xeb,0x8d,0x61,0x27,0xec,0x87,0x56,0xcd,0xd0,0xa6,0x55,0x77,0xee,0xf0,0x20,0xd0,0x59,0x39,0x95,0xab,0x29,0x82,0x8e,0x61,0xf8,0xad,0xed,0xb6,0x27,0xc3,0xd8,0x16,0xce,0x67,0x78,0xe2,0x04,0x4b,0x0c,0x2d,0x2f,0xc3,0x24,0x72,0xbc,0x53,0xbd,0xfe,0x39,0x23,0xd4,0xaf,0x27,0x84},{0x11,0xbe,0x5f,0x5a,0x73,0xe7,0x42,0xef,0xff,0x3c,0x47,0x6a,0x0e,0x6b,0x9e,0x96,0x21,0xa3,0xdf,0x49,0xe9,0x3f,0x40,0xfc,0xab,0xb3,0x66,0xd3,0x3d,0xfa,0x02,0x29,0xf3,0x43,0x45,0x3c,0x70,0xa3,0x5d,0x39,0xf7,0xc0,0x6a,0xcd,0xfa,0x1d,0xbe,0x3b,0x91,0x41,0xe4,0xb0,0x60,0xc0,0x22,0xf7,0x2c,0x11,0x2b,0x1c,0x5f,0x24,0xef,0x53},{0xfd,0x3f,0x09,0x06,0xc9,0x39,0x8d,0x48,0xfa,0x6b,0xc9,0x80,0xbf,0xf6,0xd6,0x76,0xb3,0x62,0x70,0x88,0x4f,0xde,0xde,0xb9,0xb4,0xf0,0xce,0xf3,0x74,0x0d,0xea,0x00,0x9e,0x9c,0x29,0xe1,0xa2,0x1b,0xbd,0xb5,0x83,0xcc,0x12,0xd8,0x48,0x08,0x5b,0xe5,0xd6,0xf9,0x11,0x5c,0xe0,0xd9,0xc3,0x3c,0x26,0xbd,0x69,0x9f,0x5c,0x6f,0x0c,0x6f},{0xca,0xd4,0x76,0x32,0x8b,0xbe,0x0c,0x65,0x75,0x43,0x73,0xc2,0xf2,0xfd,0x7f,0xeb,0xe4,0x62,0xc5,0x0d,0x0f,0xf9,0x01,0xc8,0xb9,0xfa,0xca,0xb4,0x12,0x1c,0xb4,0xac,0x0e,0x5f,0x18,0xfc,0x0c,0x7f,0x2a,0x55,0xc5,0xfd,0x4d,0x83,0xb2,0x02,0x31,0x6a,0x3f,0x14,0xee,0x9d,0x11,0xa8,0x06,0xad,0xeb,0x93,0x19,0x79,0xb1,0xf2,0x78,0x05},{0x85,0xe6,0xe2,0xf2,0x96,0xe7,0xa2,0x8b,0x7e,0x36,0xbd,0x7b,0xf4,0x28,0x6a,0xd7,0xbc,0x2a,0x6a,0x59,0xfd,0xc0,0xc8,0x3d,0x50,0x0f,0x0c,0x2b,0x12,0x3a,0x75,0xc7,0x56,0xbb,0x7f,0x7d,0x4e,0xd4,0x03,0xb8,0x7b,0xde,0xde,0x99,0x65,0x9e,0xc4,0xa6,0x6e,0xfe,0x00,0x88,0xeb,0x9d,0xa4,0xa9,0x9d,0x37,0xc9,0x4a,0xcf,0x69,0xc4,0x01,0xba,0xa8,0xce,0xeb,0x72,0xcb,0x64,0x8b,0x9f,0xc1,0x1f,0x9a,0x9e,0x99,0xcc,0x39,0xec,0xd9,0xbb,0xd9,0xce,0xc2,0x74,0x6f,0xd0,0x2a,0xb9,0xc6,0xe3,0xf5,0xe7,0xf4},{0xb1,0x39,0x50,0xb1,0x1a,0x08,0x42,0x2b,0xdd,0x6d,0x20,0x9f,0x0f,0x37,0xba,0x69,0x97,0x21,0x30,0x7a,0x71,0x2f,0xce,0x98,0x09,0x04,0xa2,0x98,0x6a,0xed,0x02,0x1d,0x5d,0x30,0x8f,0x03,0x47,0x6b,0x89,0xfd,0xf7,0x1a,0xca,0x46,0x6f,0x51,0x69,0x9a,0x2b,0x18,0x77,0xe4,0xad,0x0d,0x7a,0x66,0xd2,0x2c,0x28,0xa0,0xd3,0x0a,0x99,0x0d,0xba,0xa8,0xce,0xeb,0x72,0xcb,0x64,0x8b,0x9f,0xc1,0x1f,0x9a,0x9e,0x99,0xcc,0x39,0xec,0xd9,0xbb,0xd9,0xce,0xc2,0x74,0x6f,0xd0,0x2a,0xb9,0xc6,0xe3,0xf5,0xe7,0xf4}}, + {{0x02,0x3a,0x7e,0x0c,0x6d,0x96,0x3c,0x5d,0x44,0x56,0x5d,0xc1,0x49,0x94,0x35,0x12,0x9d,0xff,0x8a,0x5d,0x91,0x74,0xa8,0x15,0xee,0x5d,0x1e,0x72,0xbe,0x86,0x15,0x68,0xe7,0x36,0xa2,0x4a,0xb8,0xa2,0xa4,0x4c,0xd8,0x95,0xe3,0xc7,0xbb,0x32,0x21,0x90,0x64,0x52,0x32,0xeb,0x26,0xd3,0x4f,0xf0,0x8e,0x27,0x40,0xea,0xed,0xdb,0xf5,0xc4},{0x76,0x99,0x64,0x70,0xf4,0x50,0xc8,0xcc,0x4a,0x5a,0xa5,0x0f,0xeb,0x2d,0xc7,0x0e,0x73,0xd0,0x65,0x7d,0xc3,0xce,0x73,0x03,0x20,0x2f,0xad,0x65,0xfd,0x12,0xe4,0x7f,0xfd,0x45,0x3a,0x6e,0xc5,0x9a,0x06,0x67,0x0e,0xa6,0x7b,0x21,0x49,0x2d,0x01,0x1b,0x8e,0x03,0x6e,0x10,0x08,0x0c,0x68,0xd9,0x60,0x47,0xa4,0xe2,0x52,0xfd,0x3c,0xf4},{0xa3,0xe2,0x5f,0x16,0x39,0x78,0x96,0xf7,0x47,0x6f,0x93,0x5d,0x27,0x7b,0x58,0xe0,0xc5,0xdb,0x71,0x7d,0xa9,0x6f,0xf8,0x8b,0x69,0xdd,0x50,0xea,0x91,0x0d,0x66,0x77,0xaf,0x8f,0xd5,0x9f,0x8a,0x26,0x69,0x4c,0x64,0x37,0x62,0x81,0x6f,0x05,0x9a,0x08,0x0d,0xe1,0x69,0x24,0x77,0x3f,0x50,0xb2,0x49,0x4d,0x93,0xef,0x2e,0x87,0xff,0xde},{0xb3,0x32,0xe2,0x67,0x79,0x32,0x5f,0x64,0x47,0x49,0x1c,0xd3,0x8f,0x95,0x44,0xfd,0x4c,0x7e,0xbf,0x6b,0xb7,0xaf,0x2c,0xdd,0x8f,0xa5,0xd8,0x2f,0xbf,0xa0,0x8a,0x6b,0x58,0x25,0xc9,0x12,0x23,0x6f,0xe6,0x05,0xa8,0xd0,0x68,0x6e,0x0c,0xee,0x70,0xe4,0xa3,0x86,0x51,0x04,0x6d,0xca,0xd5,0xed,0xcf,0x74,0x1d,0x60,0x9e,0x86,0x2d,0x05},{0x91,0xf4,0x5f,0x4a,0xcb,0xd8,0xfd,0x5f,0xb9,0x3d,0x04,0xb8,0xec,0x35,0x85,0x4f,0x58,0x20,0xd1,0x1f,0x47,0xc4,0xf4,0xcb,0x21,0x4e,0x9a,0xf1,0x6e,0xbf,0xe3,0xd3,0x62,0xe3,0x82,0xf6,0xba,0xa8,0xdf,0x92,0xe2,0x3c,0xe5,0xf0,0x16,0x8a,0xeb,0xa4,0xbb,0xc7,0x81,0xaf,0x15,0x19,0x87,0x5f,0xb7,0xe0,0x4c,0x12,0xff,0x2c,0xa9,0xc8},{0xaf,0x85,0xe0,0x36,0x43,0xdf,0x41,0x17,0xda,0xde,0x5e,0xb6,0x33,0xd0,0xce,0x62,0x70,0x5f,0x85,0x24,0x6c,0x3e,0x1b,0xe1,0x52,0xc1,0x9b,0x1c,0xcd,0x61,0x80,0x9c,0xa0,0xe8,0x18,0xee,0x40,0x91,0x93,0x82,0xdb,0x33,0x44,0xff,0xd4,0xf6,0x6f,0x5d,0xf0,0x0e,0x92,0x92,0x81,0x55,0x46,0x06,0xac,0x58,0x81,0x3b,0x04,0xc7,0xf7,0x0d,0xd2,0x0c,0x08,0x6d,0x46,0xdb,0x43,0x28,0x31,0xd8,0xcd,0x87,0x50,0xbb,0xd3,0x07,0xf5,0x72,0x0b,0x15,0x7c,0x16,0xab,0x03,0xd9,0x4b,0x07,0x38,0x97,0xe8,0xd6,0xb5},{0x93,0xff,0x6d,0xc3,0x62,0xf7,0xcc,0x20,0x95,0xc2,0x2f,0x7d,0x1d,0x9b,0xd1,0x63,0xfc,0x61,0x47,0xb3,0x22,0x0f,0xca,0xb0,0x16,0xcf,0x29,0x53,0x46,0x97,0xb1,0x36,0x46,0xac,0x48,0x13,0x92,0xe4,0x46,0x68,0xcf,0x09,0x4e,0xfa,0x59,0x45,0x24,0x08,0xdb,0xb4,0x6f,0x20,0x55,0x12,0xd9,0x75,0x9d,0x8e,0x0b,0xf8,0x63,0xe0,0xf9,0x01,0xd2,0x0c,0x08,0x6d,0x46,0xdb,0x43,0x28,0x31,0xd8,0xcd,0x87,0x50,0xbb,0xd3,0x07,0xf5,0x72,0x0b,0x15,0x7c,0x16,0xab,0x03,0xd9,0x4b,0x07,0x38,0x97,0xe8,0xd6,0xb5}}, + {{0x14,0x35,0xa6,0x7d,0xc1,0xb5,0x71,0xca,0x42,0x50,0x90,0xa7,0x72,0x85,0xbe,0x78,0x7a,0x5f,0x83,0x1e,0xbe,0xef,0x6a,0xbe,0x48,0xc5,0x68,0x14,0x0c,0xf7,0x44,0x5c,0x2e,0xfd,0x1b,0xcc,0xee,0x09,0x23,0x82,0x31,0xad,0xaf,0x4b,0x73,0x9c,0xf2,0x88,0x3c,0xf3,0xb5,0x43,0x8b,0x53,0xf9,0xac,0x17,0x86,0x1c,0xc2,0x53,0x43,0xec,0x03},{0x7b,0x36,0x6c,0xcc,0xb5,0xb2,0x23,0x3d,0x7c,0xe5,0xe7,0xcf,0x06,0xe2,0x32,0x0b,0xc5,0x3b,0x7f,0x86,0x40,0xfc,0xaf,0xba,0x94,0xe0,0x88,0x58,0x5b,0xac,0xe8,0xc3,0xe8,0xc3,0xdf,0xc4,0x45,0x29,0xe8,0xf0,0x1c,0x10,0x0d,0x50,0x81,0x29,0x30,0xa8,0x27,0xb5,0x3e,0xb8,0x25,0xf1,0x17,0x30,0xc6,0x05,0xe3,0x3e,0x45,0x38,0xa8,0x3c},{0xce,0xd9,0x45,0x28,0xb0,0xce,0xa5,0x47,0xa8,0x29,0x32,0x76,0x99,0x73,0x8d,0x74,0xf9,0xed,0x0a,0xd0,0xf1,0xd8,0x7e,0x44,0x63,0x9e,0x9a,0xcf,0x7c,0x35,0x8a,0x29,0xbb,0x71,0x66,0x8d,0xa7,0xfc,0x05,0x3d,0xd4,0x4b,0x65,0x20,0xf5,0xa4,0x64,0xd8,0x9d,0x16,0x80,0x9c,0xb2,0x3c,0x3e,0xd4,0x9d,0x09,0x88,0x8e,0xbb,0x58,0xf8,0x77},{0xe1,0x29,0xb3,0x16,0xe6,0xa0,0xdb,0x64,0x08,0x36,0xdc,0x33,0xad,0x8b,0x30,0x26,0x17,0x56,0xd7,0x34,0x17,0xd1,0xdd,0x23,0x38,0x58,0x25,0x01,0x42,0x5a,0x9d,0x18,0x3e,0xac,0x31,0xfa,0x43,0x28,0xc4,0x65,0xfb,0x30,0x2f,0x8c,0x16,0x52,0x32,0x1b,0x19,0xb7,0x31,0xf6,0x67,0xa7,0xd8,0xed,0x9a,0xa3,0x95,0x01,0xd7,0xb9,0xe7,0xcc},{0x81,0x2d,0x11,0xa9,0x11,0xf1,0x22,0xe2,0x67,0x70,0xc4,0xba,0x34,0xa1,0x75,0x8c,0xf6,0x0c,0x63,0xe7,0x01,0x3c,0x64,0x6c,0xe8,0xd0,0xf8,0x8e,0x88,0xdf,0x5c,0x61,0x68,0x5d,0x1f,0xeb,0x83,0x1f,0x40,0xb8,0xa8,0x56,0x57,0x26,0x81,0x2c,0xa3,0x0e,0x48,0x4c,0x45,0x4d,0x0d,0x3d,0x6e,0x99,0x52,0xbd,0x0b,0xd8,0x05,0xc5,0xf9,0x61},{0x92,0x45,0xbe,0xe6,0xb4,0x7a,0xfa,0x28,0xd4,0x5b,0x6b,0x17,0xc6,0x13,0x61,0x5d,0x5f,0xd7,0x90,0xbb,0x89,0x35,0x7a,0x02,0x50,0x57,0x56,0x5f,0x19,0xb5,0xb6,0xc5,0x77,0x1e,0x1b,0xc0,0xd7,0x7a,0x29,0xbd,0xe7,0x24,0x01,0x2d,0x37,0xc0,0x38,0x6f,0xc8,0x35,0xa1,0x1b,0xe0,0xea,0x16,0xad,0xbc,0xdc,0xd4,0x8d,0x4e,0x71,0xdb,0x05,0x9e,0xb5,0x53,0x6b,0x5c,0xf1,0x7d,0x15,0x8b,0xd7,0xc7,0x8b,0x89,0x9d,0xfd,0x28,0x7c,0xa1,0x31,0xe2,0xf0,0x2c,0x3a,0x8d,0x0e,0x23,0x85,0x4e,0xf0,0xd1,0xc0,0x83},{0x7b,0x88,0xeb,0x45,0x1c,0x7f,0xfd,0xbe,0xba,0xac,0x53,0x28,0x59,0xe8,0xad,0x28,0xf1,0x97,0x2d,0x6c,0x31,0xa6,0xae,0x47,0x10,0x69,0x68,0x55,0xa6,0x9c,0x03,0x62,0xb7,0x2f,0x31,0x46,0x2a,0x2b,0x98,0xdd,0xe9,0xf9,0xfe,0x77,0x71,0x41,0x54,0xf8,0x59,0x02,0x7a,0xe3,0x45,0x67,0xb6,0xf7,0x94,0x31,0x3e,0x62,0x62,0x2a,0xf9,0x0a,0x9e,0xb5,0x53,0x6b,0x5c,0xf1,0x7d,0x15,0x8b,0xd7,0xc7,0x8b,0x89,0x9d,0xfd,0x28,0x7c,0xa1,0x31,0xe2,0xf0,0x2c,0x3a,0x8d,0x0e,0x23,0x85,0x4e,0xf0,0xd1,0xc0,0x83}}, + {{0x27,0x4d,0x84,0x08,0x95,0x84,0xc8,0xeb,0x1c,0x9a,0x0f,0xca,0x09,0x6f,0x48,0x8b,0x2b,0x06,0xa0,0xae,0xf2,0xe3,0x8a,0xfe,0xd7,0x52,0x4b,0xf2,0xc6,0x7c,0xc1,0x55,0x87,0x2e,0x5a,0xb4,0xc2,0x43,0x0a,0x0d,0xd0,0x00,0xa8,0xe1,0x46,0x68,0x79,0xd8,0x8c,0x01,0x36,0xb7,0x5a,0x61,0x04,0xe9,0x7e,0xbb,0xc9,0xee,0xaa,0x12,0x13,0xda},{0x78,0x66,0xd0,0xa2,0x50,0x82,0x8d,0xb0,0xa0,0x20,0xac,0xa4,0xb6,0xa0,0x31,0xf7,0x7d,0x93,0x37,0x67,0xbb,0x60,0xa2,0x1e,0x36,0xce,0x3d,0x48,0x1d,0x79,0x99,0xa5,0x19,0xd8,0x89,0x1b,0xcb,0x14,0x87,0xb7,0x62,0xfd,0xd2,0xef,0xbb,0x13,0x41,0x4d,0xf1,0x77,0x5c,0x7f,0x6c,0x3b,0x94,0x7d,0xb4,0xba,0x87,0x3e,0xc8,0xe1,0x3c,0x0a},{0xd9,0x9e,0x14,0x89,0xd6,0xf8,0x49,0xa2,0xe2,0x19,0xfe,0x94,0xaa,0xf7,0x35,0xf9,0x4a,0xf8,0xf3,0x18,0x68,0x96,0x47,0xc6,0x23,0x7c,0xb0,0x53,0xcb,0xd8,0x90,0x31,0xb7,0x50,0x0e,0x06,0xc3,0x84,0x75,0xf1,0xac,0x16,0x4d,0xc1,0xbe,0xf1,0x80,0x33,0x47,0x56,0x6f,0x33,0x94,0x5c,0x81,0x03,0x4c,0x2f,0x6d,0xac,0x73,0xba,0x91,0x3c},{0x2f,0xa9,0xb6,0xe8,0x73,0xe2,0xef,0x6d,0x6d,0xd7,0x2e,0xa0,0x51,0x61,0x24,0x81,0x8c,0xa8,0x47,0x40,0xe1,0xc7,0x75,0x79,0xc8,0xec,0xb2,0x23,0x41,0xad,0x61,0x3b,0xea,0x8a,0xdf,0x63,0xed,0xe1,0x8e,0x50,0x70,0x6e,0x86,0xed,0xb0,0xba,0x27,0x48,0x8e,0xb9,0x63,0x39,0x78,0x58,0x4f,0x1e,0xbc,0x45,0xf3,0xf2,0x3a,0x73,0x9b,0x8c},{0xad,0x42,0xc5,0x84,0xca,0xe1,0xe1,0x23,0x2a,0x73,0x15,0x3c,0x9a,0xfe,0x85,0x8d,0xa3,0x2c,0xcf,0x46,0x8d,0x7f,0x1c,0x61,0xd7,0x0e,0xb1,0xa6,0xb4,0xae,0xab,0x63,0xc4,0x0e,0xf2,0xa0,0x5d,0xa6,0xf3,0x5d,0x35,0x41,0xea,0x03,0x91,0xb1,0x3a,0x07,0xe6,0xed,0x6c,0x8c,0xcb,0x75,0x27,0xf1,0x26,0x58,0xf0,0x62,0x57,0xe4,0x33,0x00},{0x1f,0xed,0x53,0xc6,0xef,0x38,0x26,0xa4,0x18,0x88,0x8f,0x5c,0x49,0x1c,0x15,0x7d,0x77,0x90,0x06,0x39,0xe0,0x7c,0x25,0xed,0x79,0x05,0x66,0xe0,0x5e,0x94,0xe3,0x46,0x6f,0x96,0xd8,0xc1,0x11,0xa4,0x11,0x6f,0x78,0x42,0x8e,0x89,0xc7,0xc3,0xed,0xd2,0x9e,0x68,0x47,0x79,0x89,0x23,0x70,0x14,0x21,0x60,0x2d,0xfe,0x37,0x4b,0xc8,0x0a,0x16,0x73,0x7c,0xc4,0x55,0x3f,0x25,0x04,0x08,0x75,0x74,0x68,0xbc,0xe4,0x3a,0xae,0x4c,0x0e,0xd2,0x85,0xa1,0xbc,0x81,0xc0,0xc9,0xfe,0x9a,0x44,0x7b,0x83,0xdf,0xc7},{0x27,0x77,0x97,0x84,0x0f,0x2d,0x8d,0x33,0xb8,0x4e,0xdb,0x8b,0xea,0x58,0x52,0x88,0x95,0x88,0x55,0x5f,0xb8,0xc4,0xc9,0xd6,0x1f,0x1e,0xee,0x60,0xb5,0xeb,0x78,0x72,0xb5,0xe5,0x22,0x2b,0x7f,0x5e,0xc7,0x9b,0x29,0x55,0x8e,0x2a,0xfc,0x65,0x55,0x4a,0x02,0xad,0x64,0x06,0xd4,0x25,0xe1,0x96,0x6f,0xee,0x96,0xcd,0x29,0xc6,0x64,0x00,0x16,0x73,0x7c,0xc4,0x55,0x3f,0x25,0x04,0x08,0x75,0x74,0x68,0xbc,0xe4,0x3a,0xae,0x4c,0x0e,0xd2,0x85,0xa1,0xbc,0x81,0xc0,0xc9,0xfe,0x9a,0x44,0x7b,0x83,0xdf,0xc7}}, + {{0x5e,0xc5,0x5b,0x9c,0xdb,0x14,0x05,0x18,0x6b,0xe2,0x1d,0x16,0x77,0x22,0x0e,0xd2,0xe4,0x57,0x82,0x6e,0x5b,0xc5,0x6a,0xb9,0x34,0x20,0xdb,0x72,0xe2,0xe1,0xeb,0x1b,0x34,0x00,0x04,0xbf,0x83,0xf6,0x4f,0x12,0x45,0x08,0xf0,0x95,0x2a,0xdc,0x3a,0x14,0xb3,0x29,0x0b,0x99,0xcd,0x73,0x31,0xbd,0x04,0xbb,0x49,0x1c,0xde,0xcf,0x09,0x9e},{0x15,0x80,0x3e,0x2a,0xfb,0xc0,0x8d,0x62,0x19,0x27,0x83,0x04,0xcc,0xf5,0xd1,0xbb,0x40,0x41,0xbe,0x93,0x59,0x6e,0x27,0x6d,0x95,0x24,0x0a,0x07,0x27,0x86,0x10,0x75,0xf7,0x0a,0x11,0xfc,0x53,0xd0,0x4c,0x15,0xf8,0x6e,0x22,0x3f,0xeb,0x12,0x97,0x8a,0x3d,0x69,0xd8,0x96,0xc9,0x53,0x10,0x9c,0x02,0x95,0xe4,0xd3,0x1a,0xd5,0x43,0x82},{0x40,0x09,0x2c,0x17,0x7e,0xba,0xce,0x1f,0xfc,0xc1,0x8e,0xc3,0x1c,0xa2,0x34,0x52,0x78,0x16,0x23,0x71,0x82,0x40,0xf8,0x6d,0x67,0x65,0x67,0x50,0x53,0xd9,0xc8,0x5e,0x7e,0x8a,0x98,0xa3,0xc6,0x2a,0x4d,0x27,0xf3,0xb9,0xbb,0xae,0x43,0x29,0x6e,0x02,0x1c,0xe9,0x01,0xd6,0xcd,0xd8,0x91,0x44,0x95,0x2b,0x9e,0xa5,0x4f,0xd0,0x00,0xb9},{0x3a,0xe8,0x3d,0xb3,0x32,0xdc,0xc2,0xc8,0xe3,0x36,0x2f,0xc9,0x30,0x3a,0xc0,0x76,0x56,0xd3,0x0b,0x06,0xbe,0x8f,0xe7,0xf1,0x66,0x61,0x25,0x42,0x28,0xdc,0x08,0x81,0x84,0x3a,0x57,0x96,0x27,0xa6,0xcf,0xd6,0x8f,0x35,0xa2,0xc3,0x76,0x86,0x4f,0xcf,0x5f,0xa1,0x85,0x28,0x4f,0x4a,0x3a,0xbb,0x5c,0x25,0x4b,0xcc,0x46,0xfe,0xf2,0x04},{0x62,0xc8,0xa2,0x0a,0x59,0xb8,0x97,0xd2,0x68,0x94,0x00,0x3b,0x01,0xac,0x91,0x6e,0x97,0x8e,0x08,0xe3,0xfe,0x9f,0x9e,0x9f,0x4b,0xcc,0x5d,0x1d,0xb9,0xbf,0x07,0x83,0xfe,0x51,0x2a,0xdf,0x79,0x2e,0x07,0xc9,0x98,0x9b,0xbe,0xb6,0xe4,0x0a,0x20,0x44,0x86,0xea,0xb1,0x61,0x58,0x11,0x32,0x8e,0x7b,0xb9,0x67,0x2d,0xf0,0x78,0xb2,0x93},{0x1a,0x65,0xb3,0x6f,0xa2,0x45,0x29,0x53,0xd7,0x23,0x4d,0xff,0x8e,0xe9,0xb9,0xef,0x16,0xa0,0xdd,0x48,0xdf,0x70,0xd2,0xe1,0x56,0xca,0xd1,0xd0,0x4a,0x9d,0x63,0x92,0x2b,0xfd,0x7b,0x87,0x39,0x3c,0x12,0xc7,0xe5,0x91,0x31,0x95,0x78,0xc4,0x58,0x95,0x89,0x6e,0x2c,0x90,0xb4,0x0b,0xb2,0xfe,0x52,0xc0,0x86,0xc4,0x2e,0x56,0x97,0x0c,0x20,0xf2,0xbc,0x6a,0x9b,0x89,0xfb,0xe9,0x85,0x95,0xd6,0x22,0x5e,0x4d,0x6d,0x83,0x9d,0xf4,0xbe,0x66,0x05,0x32,0xb6,0xe2,0xf1,0x96,0x42,0xa4,0xc8,0x8c,0x1b,0xec},{0x43,0x85,0xff,0xb9,0xcf,0x04,0x83,0x40,0x70,0x3a,0x9c,0x48,0xb4,0xc2,0x99,0x3b,0xa0,0x39,0xf1,0x39,0x58,0x7f,0xd2,0x49,0x94,0x3c,0xc3,0xe1,0xb6,0x56,0x38,0x55,0x6f,0xb5,0x1a,0x90,0xa2,0x04,0x2f,0x19,0xf8,0xb1,0x65,0x5a,0xad,0xcd,0x1c,0x56,0x42,0x38,0xc2,0x52,0x09,0xd6,0x41,0x98,0x5d,0x5f,0xa5,0xe7,0xc2,0x55,0xa1,0x09,0x20,0xf2,0xbc,0x6a,0x9b,0x89,0xfb,0xe9,0x85,0x95,0xd6,0x22,0x5e,0x4d,0x6d,0x83,0x9d,0xf4,0xbe,0x66,0x05,0x32,0xb6,0xe2,0xf1,0x96,0x42,0xa4,0xc8,0x8c,0x1b,0xec}}, + {{0xf2,0x4a,0x96,0x57,0xc3,0x2f,0xe6,0x9f,0xed,0x7f,0xcc,0xe9,0xea,0xbe,0xd2,0x23,0x4e,0x47,0x13,0xd9,0x53,0x19,0x31,0x14,0x0a,0xd3,0x9b,0x95,0xa7,0x9c,0x88,0x5e,0x08,0xb2,0x16,0xda,0x45,0x61,0x1d,0x6b,0xdf,0xb1,0x14,0x0c,0x66,0xfd,0x3a,0xbe,0x25,0xdc,0xfd,0xcd,0xcc,0x5e,0x28,0x77,0x5a,0xa9,0x8b,0x84,0x77,0x26,0x9d,0xa6},{0xea,0xde,0x4d,0xab,0x09,0x02,0xbf,0x90,0xf8,0xae,0x8b,0x50,0x01,0xb2,0x9d,0x7c,0x0a,0x3b,0x60,0xda,0x34,0xa9,0xbb,0x4d,0xa5,0x53,0x18,0x65,0xec,0xaa,0xc9,0x29,0xb2,0xf7,0x74,0x14,0x63,0x5f,0x88,0xcf,0x4e,0x70,0x1b,0x11,0x64,0x73,0x15,0x6b,0x5a,0x8c,0xb8,0x4e,0x0f,0x83,0xae,0x4b,0x5c,0x52,0x1c,0x6a,0x0f,0x54,0x77,0xc8},{0xae,0xff,0x55,0xbf,0x78,0xb5,0xde,0x33,0xeb,0x87,0xea,0x13,0x7d,0x36,0x22,0x06,0x32,0xc4,0x7e,0xca,0x65,0x37,0xcc,0x83,0x0e,0xda,0x54,0xb3,0xd2,0xe6,0xe7,0x7f,0xe1,0x90,0x11,0x25,0x16,0x83,0x25,0x43,0xb4,0x38,0x06,0xbb,0x6c,0x62,0x7d,0x84,0x1f,0xf3,0x7b,0xeb,0xae,0x50,0xd8,0xfb,0xb9,0xf2,0xf9,0xc3,0x6f,0x59,0xb7,0xb0},{0x95,0x15,0x83,0x19,0x56,0x9c,0x11,0xd8,0x31,0x87,0x1d,0xe3,0x3f,0x07,0x89,0xb2,0xcb,0x81,0xf0,0xeb,0x0b,0x1e,0x74,0x08,0xa2,0x4a,0x0e,0x82,0xc6,0x45,0x8c,0x32,0xb4,0x8f,0xfd,0x76,0xeb,0x5e,0xc7,0x62,0xdc,0xcb,0xee,0xad,0xcf,0xcf,0xea,0x33,0x9d,0xb0,0x02,0x64,0x66,0x77,0x14,0x97,0x0c,0x6e,0x79,0xe8,0x58,0x32,0x0f,0xe6},{0xcb,0x2f,0xaf,0x53,0xd8,0x41,0x48,0x41,0x6f,0x36,0x78,0x80,0x83,0x5c,0x0d,0x4c,0x1b,0xf4,0x39,0xe0,0x34,0x4f,0xc2,0xb2,0x4e,0xf0,0xac,0xc2,0xf8,0x15,0x7a,0x81,0x9f,0x46,0x2b,0xe3,0xb9,0x39,0x05,0x89,0xa2,0xda,0x1a,0x63,0x51,0xb4,0x78,0x0f,0xfe,0x2f,0x9d,0xce,0x99,0x38,0xa9,0x7e,0xcb,0x80,0x57,0x9f,0xa2,0x28,0x0f,0x6a},{0x1b,0xec,0x67,0x50,0xd1,0x28,0x65,0x55,0xb8,0xde,0x3b,0x2e,0x1e,0x33,0xd8,0x1b,0xba,0x2e,0x78,0x6a,0xb8,0x0b,0x8c,0xa0,0x55,0x34,0x25,0x90,0x9a,0xe2,0xf5,0xaa,0x95,0x0c,0x6f,0x2a,0xb0,0x92,0x1d,0x48,0x5b,0x56,0x8c,0x82,0x8f,0xa7,0x15,0x75,0x26,0x61,0x85,0xc8,0x7d,0xda,0xf5,0x2a,0xf3,0x3c,0x34,0xc1,0x20,0x67,0xbb,0x04,0xec,0x7c,0xe2,0xcb,0x31,0xcf,0x23,0xda,0x5d,0x8a,0x05,0x00,0x9b,0x23,0x34,0xd0,0xed,0x56,0x10,0x0a,0x90,0x6b,0x73,0x26,0x6b,0xf0,0xd7,0xbc,0xd8,0xc7,0x89,0xc8},{0x90,0x43,0x54,0x87,0x44,0x00,0x07,0xca,0xa8,0x2b,0xec,0x55,0xa0,0xd2,0x8c,0x07,0x03,0xaa,0x61,0x1a,0x7d,0x0f,0x90,0x13,0x67,0x99,0x46,0x20,0xcd,0x70,0xcb,0xa7,0x96,0xdf,0x0c,0x13,0xc4,0x41,0x11,0xd6,0xc3,0x33,0x02,0x96,0x4f,0x1d,0xbd,0x06,0xa9,0xa1,0x31,0x0a,0xc3,0xdf,0x6d,0x52,0x6c,0xc6,0xbe,0xc5,0xb6,0x2a,0xb1,0x0f,0xec,0x7c,0xe2,0xcb,0x31,0xcf,0x23,0xda,0x5d,0x8a,0x05,0x00,0x9b,0x23,0x34,0xd0,0xed,0x56,0x10,0x0a,0x90,0x6b,0x73,0x26,0x6b,0xf0,0xd7,0xbc,0xd8,0xc7,0x89,0xc8}}, + {{0x4f,0x3a,0xdd,0x0f,0xcf,0x7f,0x27,0xda,0x27,0xc4,0xa6,0x2b,0x6b,0xd1,0x9f,0x59,0x73,0x5f,0xd4,0xb7,0xf0,0x86,0x16,0xc9,0xdd,0xa6,0xf9,0x9b,0x17,0xb2,0xb9,0x71,0xe7,0x4c,0xa1,0x17,0x79,0xe0,0xcc,0xae,0x10,0xec,0x28,0x3a,0x09,0xf2,0x8b,0x34,0x9c,0xac,0x16,0x2a,0xa9,0x21,0xe8,0xa7,0x18,0xc0,0xc4,0x9f,0x30,0xa0,0x25,0x62},{0x23,0x4c,0xd4,0xae,0x52,0x30,0xf6,0x64,0xb9,0xe1,0x47,0xca,0xf8,0xf3,0x3a,0x6b,0x8b,0xf3,0x29,0xe2,0x9b,0x5d,0xbb,0x0a,0x60,0x52,0x03,0x40,0x53,0x5c,0x9e,0x35,0x03,0xd4,0xec,0xd7,0x67,0xf4,0x92,0xd2,0x98,0x96,0xf2,0xa7,0xf4,0x25,0x6a,0x80,0x9c,0x75,0xc6,0xf2,0x1f,0x67,0x11,0x00,0x0d,0xda,0x1e,0xb2,0x58,0xa7,0x8c,0x39},{0x55,0x1b,0x80,0xbb,0xf3,0xc5,0x1a,0x84,0x34,0xf5,0x0a,0x8a,0x8a,0xe1,0x8c,0xea,0xa6,0xfb,0xd0,0x26,0xc9,0xa2,0x30,0x37,0x3e,0xba,0x98,0xfe,0x81,0x8a,0x52,0x37,0x0b,0x74,0x4e,0x3d,0x26,0x8f,0x82,0x4b,0xc0,0x6a,0x01,0x10,0x91,0x8f,0x89,0xb5,0x62,0x3f,0x1e,0x70,0xcc,0x25,0x77,0x39,0x74,0x88,0xdd,0xbc,0xbe,0x72,0x08,0x63},{0xe2,0x9a,0x46,0xd2,0x74,0xdc,0x0f,0x8a,0xa3,0xbd,0x20,0xb7,0xc7,0xd9,0x83,0x4b,0x58,0xa6,0xe3,0xbd,0xc5,0x00,0xb6,0x18,0x04,0x25,0x81,0xbd,0x99,0xb3,0xb1,0x2a,0x7a,0x68,0x6d,0xe1,0x3e,0x23,0x8d,0x29,0x9e,0x7a,0x30,0x56,0x4c,0x22,0xb6,0xf4,0x7d,0x7d,0x4f,0xfd,0x76,0xa5,0x9d,0x05,0x41,0x7c,0x7a,0x2d,0x7b,0xbe,0xcf,0x73},{0x7b,0xae,0x11,0x86,0x8a,0x38,0xbd,0x56,0x3c,0xf3,0x3c,0x9c,0x49,0xa4,0x68,0x0f,0x2b,0xdf,0xf2,0xa1,0xbc,0xc2,0xed,0x08,0x09,0x96,0xd0,0x7e,0x9b,0xe3,0x0a,0x72,0x13,0x03,0xd4,0x35,0x0a,0x94,0x60,0x09,0x4a,0xaa,0xca,0x35,0x8e,0xed,0x12,0xdd,0x26,0x8f,0xf8,0xa9,0xa2,0x8a,0x7f,0xac,0xf3,0x09,0xc7,0x22,0xc5,0x73,0xec,0xa0},{0xe9,0xc5,0x57,0x0d,0x85,0xbf,0x10,0xe2,0xd1,0xf5,0xd7,0x22,0xe9,0x6a,0x67,0x8d,0xd3,0x9f,0x1a,0xef,0x7f,0xc0,0x2b,0xe1,0xfd,0x2c,0xc2,0x5f,0x39,0xf9,0x34,0xd0,0x87,0x94,0x41,0x8a,0x65,0xa5,0x20,0x48,0xa4,0x20,0x5f,0x7a,0xc7,0x37,0x00,0x60,0x59,0x84,0x2a,0x1d,0xff,0x02,0xc3,0xe8,0x20,0xaa,0x39,0x13,0xac,0xf3,0xd7,0x05,0xbd,0xef,0x11,0x66,0x71,0xb8,0x9f,0x1e,0xe5,0xee,0x2e,0x37,0xfb,0x34,0xed,0xc5,0xa4,0x40,0x6e,0x38,0x31,0x0a,0x1c,0xaf,0x0d,0xd3,0x98,0xac,0x12,0x40,0xea,0x9c},{0xc6,0xcd,0x7a,0xbd,0x14,0xdb,0xe4,0xed,0xbf,0x46,0x70,0x23,0xbd,0xdb,0xc3,0xce,0x60,0xd5,0x6b,0x17,0x4c,0x23,0xfa,0x78,0x05,0xcc,0x18,0xed,0x42,0x03,0xa5,0xb7,0xdf,0x28,0x0e,0xd4,0x5d,0x31,0xd8,0xb9,0xdc,0xe9,0xf6,0x26,0xc5,0xe1,0xb3,0x80,0x0d,0x62,0xaf,0x2d,0xbd,0xd6,0xe4,0xbb,0x16,0x82,0xc8,0x13,0x2a,0x6f,0xb9,0x06,0xbd,0xef,0x11,0x66,0x71,0xb8,0x9f,0x1e,0xe5,0xee,0x2e,0x37,0xfb,0x34,0xed,0xc5,0xa4,0x40,0x6e,0x38,0x31,0x0a,0x1c,0xaf,0x0d,0xd3,0x98,0xac,0x12,0x40,0xea,0x9c}}, + {{0x6f,0x46,0xcd,0x96,0xc4,0x13,0xf4,0x11,0x62,0x49,0x8c,0x5c,0x78,0x27,0xef,0xc8,0xb9,0xe2,0x7d,0xf1,0x0d,0x37,0xf2,0xfe,0x85,0x35,0x82,0x60,0x23,0xb6,0x7b,0x17,0xd2,0x91,0xef,0x01,0x9e,0x99,0x35,0xab,0xc7,0xfb,0xa1,0xa3,0x13,0x44,0x3f,0x3c,0x16,0xcb,0xd8,0xf0,0xbf,0x9e,0x65,0x4d,0x07,0xe0,0xfd,0x8e,0x32,0x61,0x95,0xd5},{0xb7,0x81,0x16,0x2f,0xcb,0xa4,0x30,0x4e,0x6d,0xf5,0xf0,0x3f,0xfe,0xd9,0x81,0x20,0xa6,0x0e,0x2b,0xa8,0xc5,0xed,0x0d,0x9a,0x28,0x9c,0xe3,0xa9,0xb7,0xbf,0x87,0x0f,0xa5,0xf9,0x33,0xe7,0xa6,0x7f,0x9b,0xac,0xb6,0xcc,0xaf,0xfc,0xa7,0x4a,0x4d,0x36,0x39,0xa9,0xb6,0xf5,0x09,0xde,0x8d,0x37,0x11,0x07,0xd1,0x8a,0xf5,0x7b,0x66,0xe1},{0xcc,0xe0,0x07,0x62,0xbe,0x10,0x8c,0x3a,0xa2,0x96,0x5d,0x11,0xc7,0xd5,0x50,0xc3,0xbb,0x55,0x21,0xc5,0x40,0x27,0x7d,0xdb,0xad,0xd2,0x61,0x2a,0x42,0x5f,0x94,0x23,0x77,0x83,0x3a,0x99,0xe8,0xda,0x79,0x8c,0x1e,0xa8,0x44,0x04,0xec,0xf5,0xd1,0x55,0x1e,0x58,0xf1,0x6e,0x4d,0x27,0xa4,0x91,0xec,0x59,0xc8,0x17,0x36,0x58,0x2a,0x1f},{0x6d,0xf8,0x73,0xa3,0x38,0x61,0x1d,0x95,0x09,0xde,0xe5,0x26,0x1b,0x15,0x16,0xfb,0xf5,0x16,0xa8,0xf3,0x9e,0x3a,0x6b,0xb5,0x8c,0xee,0xa8,0x66,0x79,0xc3,0x9e,0xb4,0xe1,0xc2,0x85,0x0e,0x86,0x10,0x5a,0x4e,0x8b,0x4c,0x0a,0x7a,0xd8,0x8a,0x48,0xf4,0xa0,0x79,0x37,0xe3,0xa5,0x90,0x05,0x5e,0xbd,0xa1,0xf6,0x09,0x58,0x9c,0x6f,0x09},{0x66,0x47,0x6d,0x60,0x06,0x2d,0x90,0x8f,0xae,0x6c,0x01,0xe9,0xb0,0xf9,0x6b,0xa5,0x4a,0xe1,0xdb,0xd3,0x64,0x42,0x37,0x5c,0x11,0x40,0x7a,0xce,0x4e,0x83,0xc3,0x2c,0x2e,0xd2,0x67,0x76,0xfb,0x8c,0x5d,0xab,0xe8,0xb8,0xd6,0x2b,0xf8,0x86,0xff,0x96,0xf3,0xa8,0x0e,0x2b,0x1a,0x68,0xf5,0xe4,0xee,0x49,0xa6,0x8c,0x41,0x1f,0x97,0xbf},{0x81,0x92,0x4e,0xc6,0xab,0x00,0xdd,0xf9,0xf9,0xb7,0xe0,0x0a,0xa9,0x3f,0x0a,0xf9,0x32,0x73,0xf6,0x22,0xec,0x95,0xd9,0x20,0x8a,0x3f,0xeb,0x0d,0xc7,0x79,0x6f,0xb3,0x85,0xf4,0xe1,0x11,0xe1,0xcc,0xaa,0x1b,0xfd,0xf3,0x43,0xff,0x66,0x73,0x0f,0x09,0xcc,0xa4,0x6c,0xb8,0x2a,0x0f,0x53,0x58,0x63,0x32,0x06,0xd9,0x6b,0x1a,0x14,0x04,0x85,0x3f,0x2f,0x2b,0x05,0xfb,0xed,0xe9,0x08,0x0d,0x21,0x49,0xc9,0x79,0xdf,0x6f,0x77,0x89,0xd7,0x74,0x09,0x57,0x1a,0xd2,0xa7,0x43,0xbf,0x08,0x8e,0x98,0xbc,0x2f},{0xe3,0xb1,0xc4,0x81,0xe6,0xec,0x07,0x58,0xa4,0xcb,0x7e,0xd5,0xae,0x9d,0x43,0xf1,0xb7,0xe2,0x0a,0x1f,0xd5,0xe8,0x14,0xba,0x22,0xff,0xb7,0x20,0x76,0x08,0xdc,0x9a,0x44,0x4c,0x1c,0xcd,0x38,0x4d,0xb5,0xd8,0xa9,0x1b,0x9d,0xbb,0x13,0x5a,0x6c,0xe9,0x5d,0xa4,0x42,0x0e,0xde,0x9a,0x47,0x8a,0x2a,0x97,0x42,0x86,0x87,0x98,0x3f,0x04,0x85,0x3f,0x2f,0x2b,0x05,0xfb,0xed,0xe9,0x08,0x0d,0x21,0x49,0xc9,0x79,0xdf,0x6f,0x77,0x89,0xd7,0x74,0x09,0x57,0x1a,0xd2,0xa7,0x43,0xbf,0x08,0x8e,0x98,0xbc,0x2f}}, + {{0xff,0xe3,0x69,0x7b,0x62,0x45,0x40,0x5f,0x1c,0x49,0x65,0xd6,0xae,0x24,0x16,0x84,0xfa,0x69,0x6c,0x1f,0x6c,0x65,0xee,0x52,0xe9,0x6c,0x54,0xc7,0x31,0x9b,0xc2,0x74,0x4f,0xc0,0x16,0xb8,0xf8,0x75,0x5f,0x45,0xb5,0xf3,0xa0,0xd9,0xbe,0x25,0x82,0xbd,0x3c,0x03,0xe0,0x14,0x15,0x6a,0xd5,0x64,0x08,0x65,0x13,0x33,0xc2,0xab,0xe0,0x45},{0x6f,0x5a,0x90,0x80,0x25,0x13,0xc2,0xa7,0xfe,0x1c,0xa1,0x07,0x81,0x4b,0x09,0xd3,0xbd,0xda,0x55,0xa8,0xaa,0x62,0x19,0x03,0xe9,0x9f,0x77,0xef,0xff,0xd4,0x5e,0x53,0xbc,0x9d,0x71,0xb8,0xc4,0xc2,0x85,0xb9,0xb4,0x3d,0x95,0xb8,0xfd,0x44,0xb7,0xc8,0x6f,0x93,0x15,0x04,0x16,0x7e,0x01,0xf2,0x09,0x23,0x96,0x69,0xe5,0x65,0x52,0x34},{0xaf,0xfe,0x4f,0x34,0x4e,0xfe,0x51,0xa5,0xb2,0xd8,0x31,0x74,0x7b,0xae,0xfb,0xb9,0x33,0xc1,0xdc,0x66,0xe6,0x95,0x9e,0xce,0x77,0x7d,0x55,0x3c,0xa6,0x6c,0x09,0x23,0x5a,0x1a,0x5e,0x1a,0x41,0xd3,0xad,0x5f,0x86,0xd0,0x14,0xf5,0xe0,0xda,0xf1,0xce,0x19,0x90,0x45,0x0c,0x4c,0xb1,0xd3,0xc8,0x4c,0xdb,0x7e,0x49,0xf5,0xac,0xde,0xff},{0x1b,0x9b,0x6b,0x30,0xd3,0x19,0x37,0x83,0xad,0x05,0xca,0xba,0x22,0x85,0x33,0x7f,0x55,0x60,0xe3,0x14,0x8c,0x39,0x87,0xd1,0x4c,0x21,0x27,0xa0,0xae,0x4a,0x56,0x15,0x50,0x6c,0x99,0xca,0xff,0xde,0x10,0xc6,0x9f,0x6c,0x70,0xd1,0x66,0xb4,0x87,0xd8,0xfc,0x46,0xf2,0xcf,0x0c,0xd8,0xc3,0x14,0x5d,0x27,0xbd,0xed,0x32,0x36,0x7c,0xed},{0x64,0x6b,0x74,0xc7,0x60,0x36,0xc5,0xe4,0xb6,0xde,0x02,0x1a,0x09,0xaf,0x65,0xb1,0x94,0xa3,0xf4,0x95,0xf5,0xb0,0xef,0x86,0xb5,0x13,0x26,0x0b,0xe8,0xc5,0x5c,0x77,0xf5,0xe6,0xb6,0x10,0x36,0x87,0xa3,0xd2,0x7c,0x17,0x2c,0xb9,0xb0,0x90,0x9e,0x8c,0x0a,0x7d,0x73,0xb2,0x29,0xeb,0xa7,0x85,0xd7,0x04,0x14,0xf9,0x77,0xb7,0xf4,0x89},{0x7f,0x1c,0x5a,0x57,0x14,0xf6,0x30,0x07,0xf9,0xfe,0x42,0x98,0xcb,0x3d,0xac,0x04,0x30,0x0d,0xc6,0xd0,0x4f,0x8a,0xbc,0xdd,0x3e,0xc3,0xb7,0x74,0xc8,0x3b,0x1a,0xcc,0x6a,0x54,0x9e,0xb9,0xbe,0xf0,0x7c,0x35,0x35,0x1a,0x50,0x4c,0xc2,0x38,0x41,0x46,0xc8,0xc4,0x81,0x2b,0x26,0x56,0x6f,0x8a,0x9f,0x74,0x87,0xe0,0x01,0x82,0xe2,0x09,0xf3,0x9a,0xc5,0x33,0x5a,0x7d,0xb6,0xbb,0xff,0x20,0x4d,0xc1,0x99,0x3d,0xcc,0x5a,0xc7,0xd1,0xbe,0x4c,0xcf,0xc8,0x09,0x79,0x15,0x5e,0x0c,0xc6,0x26,0x36,0xe6,0xd9},{0x4d,0x2f,0x08,0x84,0x32,0xcf,0xe0,0x3b,0xa8,0x3e,0xa5,0xf8,0x3a,0xe8,0xa9,0x04,0x5a,0x74,0x67,0xcb,0x41,0x22,0xc5,0xc4,0x9a,0xa5,0xc1,0xa7,0x94,0x8b,0xa5,0x35,0x00,0x00,0x1a,0xaf,0xfb,0xed,0x40,0xb8,0x2b,0x28,0xf1,0xb1,0x02,0xd3,0x8b,0xc0,0x32,0x4a,0xa5,0x0a,0xa4,0xc3,0xbf,0xb3,0xf5,0xb7,0x65,0x8e,0x88,0xdf,0xd0,0x0e,0xf3,0x9a,0xc5,0x33,0x5a,0x7d,0xb6,0xbb,0xff,0x20,0x4d,0xc1,0x99,0x3d,0xcc,0x5a,0xc7,0xd1,0xbe,0x4c,0xcf,0xc8,0x09,0x79,0x15,0x5e,0x0c,0xc6,0x26,0x36,0xe6,0xd9}}, + {{0xc8,0x8e,0x1c,0xea,0x02,0x6a,0xfd,0x88,0x8b,0xa9,0x9d,0xdd,0xba,0xea,0x77,0x30,0x88,0x1a,0x93,0x49,0xda,0x05,0x18,0xbb,0x4a,0x6a,0x11,0xc4,0x48,0x72,0x77,0x1f,0x6e,0x2b,0x9a,0xe3,0x27,0xbe,0xe1,0x75,0x32,0x30,0xa6,0x12,0x26,0x44,0xbf,0xb2,0xa5,0x51,0x0b,0x48,0x3a,0xea,0xc5,0xd4,0x24,0x3f,0x4e,0xe8,0xe5,0xc3,0xfb,0xc2},{0xcb,0x56,0x3c,0x00,0x28,0x15,0x72,0x16,0x23,0x4e,0x2e,0x2c,0x8c,0xe8,0x7c,0x44,0x82,0x2a,0xe0,0x57,0xa3,0x0a,0xc4,0x42,0xb5,0x07,0xe1,0x1b,0x78,0x8b,0x3d,0x4d,0xcb,0xe4,0x56,0x72,0x0b,0x85,0x52,0xd8,0x55,0xe2,0xcd,0x38,0xd2,0x83,0xb6,0x05,0xd2,0x9f,0x63,0x9e,0x7f,0xca,0xe5,0x95,0x36,0x61,0x9b,0xca,0x09,0x27,0x53,0x82},{0x24,0x67,0x10,0xd6,0x8a,0x1a,0x8e,0xb8,0x53,0xef,0xb7,0x67,0x2a,0xfd,0xb8,0xd6,0xe3,0xf7,0x41,0x95,0x8c,0x50,0xca,0x1d,0x21,0x21,0x41,0xd1,0xef,0x2d,0x9b,0x53,0xa9,0x42,0xcd,0xda,0x6d,0x12,0x1b,0xbd,0x0a,0xe1,0x4d,0x95,0xc6,0xaa,0x40,0xfd,0x98,0xfb,0x26,0x21,0x5e,0xaf,0x8e,0x6b,0xc9,0x36,0x2c,0x66,0x31,0x24,0x45,0x87},{0x5e,0xf9,0x1d,0x10,0xb5,0x79,0x1f,0x80,0x85,0x90,0xc3,0x7f,0x2b,0x73,0xbf,0x83,0x0b,0x5d,0x46,0xae,0x79,0xef,0x09,0x71,0x29,0xfb,0x83,0xde,0x1f,0xe2,0xdb,0x1b,0xa2,0x22,0xee,0x50,0x21,0x9d,0x9c,0x35,0x14,0x48,0x13,0xa5,0xd1,0x68,0xf4,0x61,0x1f,0xd7,0xe2,0xd6,0x42,0x1c,0xdc,0x58,0xec,0x8b,0x03,0x6b,0xdf,0x64,0x06,0x30},{0xf9,0xa6,0x88,0x74,0x07,0x19,0x15,0x38,0xaf,0xac,0x07,0x10,0xe0,0xd9,0x22,0xf3,0x78,0xb0,0xbf,0x60,0xa3,0x0f,0xea,0x0f,0xa8,0x64,0xa9,0xa3,0x82,0xe1,0x4c,0x29,0x36,0x22,0x6d,0x43,0x9c,0xde,0x22,0xbf,0xc6,0x85,0xf7,0xe9,0xe0,0x79,0x80,0xfe,0x9d,0xd6,0x24,0xbd,0x29,0xa4,0x8c,0x35,0x21,0x87,0x45,0x7f,0x88,0xd9,0x9a,0x9d},{0x49,0x43,0x19,0x14,0xcc,0x4a,0x11,0x01,0x05,0xd1,0x4e,0x39,0x6d,0xb0,0x22,0x65,0x32,0x6e,0x67,0x04,0x50,0x85,0x53,0x42,0x90,0x2c,0xc0,0x63,0x2f,0xbd,0x15,0x90,0x1b,0x3f,0x03,0x90,0x16,0x7f,0x7b,0x49,0x74,0xd0,0x3d,0x81,0x80,0x1e,0x9e,0x2e,0xa9,0x13,0x6a,0x10,0x14,0xc1,0xfd,0xf9,0x25,0x3a,0x1d,0x52,0x93,0x0a,0x77,0x03,0xa2,0xdd,0xce,0x9f,0x2a,0x35,0xc9,0x93,0x7c,0xa2,0x2c,0xf6,0x38,0x73,0xb3,0xab,0x7f,0x55,0xb6,0x62,0xa2,0x8d,0x6a,0x3e,0x88,0x04,0x9b,0xa2,0x19,0x64,0x55,0x01},{0x22,0x03,0x49,0x58,0x76,0x3c,0x85,0x45,0x5e,0x73,0x78,0x8f,0x65,0xc9,0x50,0xf8,0xd7,0x16,0x92,0xa4,0xd1,0x79,0xce,0xf3,0x00,0x34,0x38,0xb8,0xcc,0x96,0x9f,0xa6,0x87,0x28,0xcb,0x19,0x28,0xad,0x83,0xb5,0x09,0x96,0x54,0xe8,0x2a,0xb9,0x9b,0xff,0x60,0x85,0x31,0x28,0x62,0x36,0xd2,0x0e,0xad,0x2a,0xe1,0x84,0x80,0xeb,0x6f,0x00,0xa2,0xdd,0xce,0x9f,0x2a,0x35,0xc9,0x93,0x7c,0xa2,0x2c,0xf6,0x38,0x73,0xb3,0xab,0x7f,0x55,0xb6,0x62,0xa2,0x8d,0x6a,0x3e,0x88,0x04,0x9b,0xa2,0x19,0x64,0x55,0x01}}, + {{0xeb,0x18,0x95,0x94,0x5f,0x15,0x8c,0xb8,0x4d,0x6e,0x7d,0xc0,0x96,0x6c,0x52,0xa2,0x5f,0x43,0x67,0xc2,0x3a,0x10,0x5b,0xf1,0x8f,0x21,0x89,0x06,0x77,0xe9,0xab,0x2e,0xcd,0x17,0x9c,0x9a,0xd7,0x89,0x7e,0x53,0x58,0x60,0x9b,0xce,0x90,0xd9,0x13,0x2d,0x78,0xc4,0x2c,0x1c,0x4c,0xe8,0x23,0x70,0xff,0xa0,0x42,0x98,0x25,0x40,0xd6,0xd8},{0xb6,0xfb,0xdd,0x5d,0x35,0xf2,0x2b,0x89,0xda,0x8e,0x90,0xee,0x03,0x4e,0x75,0xdb,0x4c,0x45,0xc8,0x00,0xde,0x06,0x27,0xde,0x44,0xb5,0x5b,0xc7,0x56,0xc3,0xf5,0xbb,0xee,0xa6,0x21,0xd4,0xd9,0xb9,0x24,0x9c,0x4c,0xbc,0x23,0xe5,0xeb,0x05,0xb6,0xd0,0xd0,0xbf,0x49,0x95,0x01,0xb4,0x97,0xad,0xb5,0x71,0x8d,0x4b,0x32,0xd0,0xdd,0x1a},{0xfd,0x11,0xd7,0xe4,0x46,0xcd,0xd8,0x44,0x89,0x0a,0xe7,0x44,0x59,0xe9,0xcf,0x9f,0xd6,0xf1,0x74,0x56,0x04,0x78,0xfa,0x29,0x46,0x8a,0x8d,0x1b,0xbe,0x41,0x92,0x1c,0x8d,0x74,0x01,0x1b,0xc1,0xf8,0x26,0xf4,0xc2,0x68,0xc3,0x23,0x8c,0x68,0x7c,0x0a,0xad,0xdd,0x50,0x10,0xcf,0xdb,0x78,0xc5,0x79,0x28,0x37,0x63,0x92,0x1a,0x1d,0xea},{0xd2,0x2a,0xf0,0x66,0x15,0x8b,0xcb,0x83,0xcf,0x34,0xa1,0x33,0x6b,0xd5,0xa8,0x98,0x3b,0xd7,0x09,0x0d,0x70,0xa5,0x8a,0xc0,0x73,0xcf,0xde,0x59,0xd5,0x13,0x41,0xd2,0x43,0x8b,0xb4,0xc3,0x5b,0x6f,0xf1,0xed,0x47,0x76,0xe6,0x5e,0xb8,0x2a,0x7e,0x20,0x91,0xa0,0x9d,0xc1,0xa2,0x0a,0x6d,0x97,0x7d,0xeb,0xe3,0x64,0x5f,0x86,0xff,0x3e},{0x45,0xd8,0xdc,0xe4,0x3a,0x3a,0x44,0xdc,0x7f,0xa8,0x92,0x11,0x1b,0x4f,0xfa,0xcf,0x21,0xff,0xfb,0x20,0xb0,0x02,0x6d,0x0e,0x1c,0xde,0xe8,0x51,0xd8,0x2c,0x72,0x0e,0xbf,0xf6,0x9a,0xd3,0xd3,0xfe,0xfa,0x98,0x4e,0xc2,0xf0,0x16,0xda,0x39,0x93,0xc4,0xe0,0x33,0x9a,0x43,0xe8,0x7a,0xc5,0x0f,0x0b,0xa4,0x45,0xf0,0x5e,0x7a,0xa9,0x42},{0xdb,0x4e,0x17,0x76,0x8b,0x3c,0x98,0x7f,0x58,0x76,0x97,0xc9,0x3f,0x99,0x01,0x05,0x42,0x7e,0xfd,0x83,0x99,0xaa,0x19,0xb5,0x72,0x4c,0x69,0xed,0x6e,0x21,0x79,0x6e,0x3b,0x71,0xe5,0xab,0x23,0x84,0xe7,0xfe,0x58,0x2b,0x0d,0x1e,0x75,0x7c,0x29,0xb3,0x2d,0x66,0xc2,0x45,0x88,0xac,0x86,0x29,0xe4,0xaa,0x9e,0x71,0xa1,0x88,0xf9,0x06,0xda,0xa3,0xdd,0x7b,0x6c,0xd9,0xc9,0x73,0xe9,0x56,0xd1,0xee,0x5b,0xf9,0xae,0xc0,0x29,0xbe,0x20,0x6c,0xc7,0xf9,0xc5,0x2d,0x6d,0xad,0x8f,0x49,0xf8,0x17,0xdb,0x7a},{0xb8,0xb7,0xec,0xeb,0x3e,0x40,0x77,0x6c,0xab,0x10,0xfe,0x9f,0xd1,0x40,0xfe,0xd2,0x88,0x8e,0xb0,0x55,0xae,0x75,0xb1,0xcc,0x9d,0x6c,0x11,0x28,0x95,0x38,0x9f,0xb9,0x59,0xe2,0x29,0xc3,0xbc,0x09,0x16,0x1f,0x17,0x9e,0x15,0x78,0x09,0x61,0x07,0x9e,0xad,0x67,0x98,0xa9,0x24,0xff,0xf9,0x4b,0xa2,0x76,0x09,0xa0,0xd7,0x1b,0xed,0x05,0xda,0xa3,0xdd,0x7b,0x6c,0xd9,0xc9,0x73,0xe9,0x56,0xd1,0xee,0x5b,0xf9,0xae,0xc0,0x29,0xbe,0x20,0x6c,0xc7,0xf9,0xc5,0x2d,0x6d,0xad,0x8f,0x49,0xf8,0x17,0xdb,0x7a}}, + {{0xc3,0x92,0x4d,0x01,0x9c,0xea,0x5a,0x8d,0xbd,0x5c,0x12,0x58,0x6d,0x03,0x26,0xbf,0xa4,0xdd,0xf7,0x26,0xa4,0x0d,0x22,0xe0,0xbd,0xcc,0x6f,0x30,0x9e,0xf9,0x4c,0x1f,0x03,0x52,0xab,0x38,0xe9,0x9c,0x08,0x9c,0x09,0xe5,0x87,0x5c,0x24,0x1a,0xe2,0x75,0xcb,0x18,0x8a,0x63,0x50,0xd1,0x23,0x45,0x49,0x93,0x40,0x2c,0x09,0xd4,0xac,0x39},{0xd4,0xe7,0xb7,0x05,0xfd,0xd6,0xf3,0x57,0xfb,0xc2,0x2f,0x2c,0x71,0x80,0xf5,0xc3,0xa6,0x0a,0x23,0x9d,0x1d,0xa8,0x68,0x10,0x8a,0xfa,0x68,0x9d,0x2b,0xcf,0x96,0xa9,0xe6,0x0e,0x07,0x32,0x23,0x09,0x87,0x16,0xc5,0xbb,0x76,0x22,0xfc,0xb4,0x59,0x6d,0x67,0xfd,0x29,0x51,0x95,0x4c,0xe2,0x8c,0x18,0xab,0xda,0x84,0xc3,0x62,0x80,0x14},{0xc9,0xa1,0xfe,0xc3,0x48,0x0d,0xee,0x54,0x44,0xff,0x9c,0x46,0x04,0x0e,0x74,0xda,0xa4,0x6a,0x56,0x02,0x5f,0x76,0x0e,0xb5,0xc1,0xc9,0xe9,0xb2,0x6e,0x07,0x49,0x0c,0xf7,0x4b,0xee,0xd6,0x0a,0xad,0x94,0x03,0x58,0x2d,0x60,0x95,0xf8,0x16,0x7b,0x49,0x0b,0x01,0x66,0x3e,0x17,0x01,0xe5,0x54,0x7d,0xd7,0xbb,0x10,0xd1,0xad,0xad,0x79},{0xb2,0xd8,0x10,0x29,0xeb,0xb8,0x4e,0x2b,0x39,0x85,0x5c,0xb3,0xdc,0xf5,0x87,0xca,0xca,0x9c,0x7a,0x8c,0x2b,0x08,0xe8,0x25,0xe2,0xcf,0x70,0xe2,0xe6,0xfb,0xdb,0x0c,0xc3,0x0d,0x71,0x11,0x83,0x65,0xf2,0x71,0x08,0x1b,0x32,0x6e,0x6c,0x51,0x50,0xf1,0xf6,0x4b,0x54,0x63,0x16,0x7f,0xfd,0x80,0x05,0x61,0x63,0xf1,0x80,0x6a,0x0b,0xfd},{0xa7,0x4b,0x75,0x38,0x90,0x64,0x96,0x7b,0xda,0x5e,0x08,0x9b,0x80,0xc4,0x72,0x3f,0x73,0xb2,0xdb,0xd3,0x4a,0xed,0xa4,0xdc,0x5c,0x79,0xe5,0x0f,0x7a,0xd3,0x0c,0xac,0xf9,0x99,0x5c,0x1a,0x0f,0xb3,0x1a,0x0f,0x5c,0xc3,0x9e,0x1a,0x2b,0xfa,0xc3,0xf0,0x40,0xe5,0x5f,0x36,0xd2,0x98,0x31,0xa1,0xaf,0x18,0x5f,0xae,0x92,0xf3,0x9e,0xc0},{0xf9,0xbf,0x52,0xe6,0xd3,0xe1,0x5d,0xd3,0x30,0xf3,0xa1,0x0c,0xc8,0x5a,0x97,0x55,0xab,0x67,0x67,0xd0,0x00,0x62,0x7b,0x80,0x70,0xbf,0x24,0xd0,0x09,0x8b,0x07,0x77,0xeb,0x3e,0xf0,0x5d,0xdf,0x7b,0xa9,0x7d,0xa4,0x6a,0x0d,0xf1,0xac,0x83,0x7d,0x64,0xb5,0xf4,0xc6,0xc4,0x12,0x0c,0x55,0x9f,0x67,0xbb,0xd5,0xe3,0xd3,0xdb,0x17,0x0f,0x90,0x2f,0x8f,0xc9,0xfd,0x4e,0x6c,0x8b,0xe6,0x99,0xfa,0xda,0x8f,0x1f,0xe6,0xc3,0xeb,0xd8,0x14,0x20,0xcc,0x3c,0x1c,0x23,0x77,0x28,0x9b,0x22,0x9a,0x5a,0x0c,0x43},{0xa2,0x78,0x37,0xc9,0x63,0xe1,0x31,0x36,0xc2,0x58,0xac,0xca,0xbb,0xa2,0x84,0xaa,0xb3,0x82,0xe2,0x19,0xb7,0x14,0x96,0x27,0x77,0xfa,0xa1,0x02,0xaa,0xff,0x55,0x82,0xba,0xc0,0x38,0x1a,0x69,0x35,0x48,0x87,0xc2,0xeb,0x48,0x08,0xea,0xc5,0x6b,0xfc,0x84,0x60,0x4e,0xce,0xd7,0xd2,0x86,0x8b,0x76,0xf3,0x46,0xe1,0x87,0x1f,0xff,0x09,0x90,0x2f,0x8f,0xc9,0xfd,0x4e,0x6c,0x8b,0xe6,0x99,0xfa,0xda,0x8f,0x1f,0xe6,0xc3,0xeb,0xd8,0x14,0x20,0xcc,0x3c,0x1c,0x23,0x77,0x28,0x9b,0x22,0x9a,0x5a,0x0c,0x43}}, + {{0x0e,0xa6,0x0c,0xef,0x12,0xd6,0x7d,0x71,0xd4,0x88,0x73,0x86,0x9a,0x88,0x8f,0x5b,0xd1,0xb6,0x12,0xc4,0x93,0x8b,0x5f,0xee,0xdd,0x9c,0x2a,0x7f,0x4d,0xfd,0xba,0x00,0x09,0x45,0x77,0xd2,0xcf,0xcd,0x3a,0x6f,0x27,0x44,0xe2,0x55,0x3e,0x79,0x88,0x4d,0x5f,0x38,0x34,0xe8,0xe7,0xc6,0x3a,0xde,0xef,0x99,0x15,0xea,0x88,0x79,0xd7,0xca},{0xa0,0x9a,0x0a,0x3a,0x42,0x35,0x54,0x78,0xb9,0x82,0x52,0xb4,0xc8,0x5c,0x4a,0x03,0xa1,0xb9,0x27,0xcc,0x99,0xec,0x03,0xdf,0xdd,0x6e,0xde,0xef,0x8f,0x7f,0xdc,0x5a,0xc3,0xcb,0x0e,0xa2,0x7e,0x93,0xe6,0xdd,0xbd,0xf1,0x1b,0x03,0x29,0x63,0x72,0x11,0x72,0x3d,0x24,0x6f,0xdf,0x8e,0xed,0xa4,0xe2,0x2a,0x4c,0x00,0xe2,0xc4,0x55,0x1b},{0xb2,0xf1,0xff,0xf6,0x3a,0x26,0xe1,0x74,0x52,0xba,0xee,0x28,0xb6,0x56,0x90,0x59,0xde,0x92,0x5f,0x84,0xd1,0x87,0xe2,0x64,0xce,0xdc,0x94,0x3c,0xb4,0xf8,0x01,0x0a,0x86,0x2f,0xfe,0x79,0x03,0x72,0xfc,0x26,0x21,0xc3,0x1e,0xec,0x63,0x29,0x64,0xcb,0x5f,0xcc,0xb6,0x78,0xf7,0xc8,0xd1,0xf8,0x5c,0xc4,0x4b,0xc0,0xc3,0x75,0x3e,0x46},{0x03,0x4b,0xb9,0xd1,0x50,0xa3,0x79,0xbe,0x74,0xa3,0xb5,0xd8,0x28,0x1b,0x6d,0x72,0x68,0x0a,0x9b,0x19,0xc9,0x13,0xc4,0x04,0x94,0x0a,0xcb,0x72,0xff,0x7d,0xb6,0x9a,0x1c,0xfd,0xe4,0xa3,0x75,0x13,0x57,0x36,0xfe,0x4a,0xf6,0xbc,0xca,0xd9,0x34,0x9b,0xef,0x90,0x02,0xd9,0xbd,0xdd,0x6f,0x22,0x54,0x36,0xb2,0x3f,0x22,0x65,0xef,0xe7},{0x04,0xd4,0x43,0xe8,0x8c,0xc4,0xfb,0xe5,0x55,0xd0,0xa4,0xea,0x20,0xf8,0xe1,0x8f,0xc2,0xbc,0x1f,0x55,0xf1,0x8d,0xda,0xc0,0x85,0xa4,0xef,0x36,0x97,0x22,0x8b,0x8e,0x77,0x4c,0x1a,0xa4,0xa0,0x6f,0xe1,0xdc,0x32,0x47,0xc4,0x3a,0xd8,0x8a,0xbd,0x19,0x30,0x1c,0x96,0x7a,0xb2,0x23,0x7c,0x16,0x03,0xa7,0x4f,0xfd,0xa6,0x50,0xd9,0xf7},{0xdf,0xc2,0x59,0xd2,0xa9,0x9b,0x1e,0xca,0xf0,0x39,0x2f,0xf8,0xc2,0xf3,0x91,0x55,0x1b,0xba,0x81,0x3a,0x67,0x1a,0xd4,0xf4,0xb0,0x9f,0xb6,0x18,0x38,0x65,0x3e,0x67,0xa0,0x37,0xc2,0x9a,0xc7,0xee,0x72,0x8e,0x13,0x64,0xd1,0x0a,0xda,0xbd,0x8d,0xa4,0x28,0x55,0x3a,0x2c,0x78,0x41,0xc6,0xfc,0x1c,0x0f,0xf8,0xd7,0x5f,0xe6,0xde,0x0b,0xd5,0xc0,0xaa,0x2c,0x5c,0xac,0x46,0xeb,0xa4,0x35,0x2a,0xab,0x00,0x2e,0xc0,0x8b,0x42,0x65,0x2f,0x2f,0x13,0x84,0x60,0x15,0xa3,0x69,0xee,0xab,0x0e,0x50,0xbf,0x5f},{0xc1,0xb0,0xac,0x4c,0xfa,0x62,0x52,0x22,0xae,0x8c,0x94,0x38,0xd9,0x6e,0x10,0x94,0xe7,0xaa,0xc0,0x92,0x93,0x06,0x55,0xf9,0x2e,0xd9,0x10,0x4d,0xcb,0x82,0x19,0x1f,0x27,0x16,0x81,0xdd,0xea,0x7a,0xa8,0xce,0x5a,0xdd,0x37,0x77,0x24,0x57,0xfb,0x40,0x3d,0x1b,0x48,0x88,0xda,0xce,0xe8,0xd2,0xed,0xe0,0x6e,0x29,0xeb,0xdb,0x95,0x09,0xd5,0xc0,0xaa,0x2c,0x5c,0xac,0x46,0xeb,0xa4,0x35,0x2a,0xab,0x00,0x2e,0xc0,0x8b,0x42,0x65,0x2f,0x2f,0x13,0x84,0x60,0x15,0xa3,0x69,0xee,0xab,0x0e,0x50,0xbf,0x5f}}, + {{0x3a,0x79,0x39,0x60,0xe9,0x93,0xad,0x78,0xf9,0x0b,0x99,0x64,0x71,0x76,0xad,0xdc,0x63,0xa3,0x38,0xbf,0x0a,0x36,0x22,0xcf,0x4f,0x84,0x3e,0x34,0xaf,0x0b,0xd4,0x5c,0xc0,0xa4,0x01,0x7c,0x07,0xc3,0xb4,0xcb,0xdb,0x39,0xdd,0x39,0xc7,0x5c,0xbd,0xcf,0x61,0x8b,0x72,0x74,0xd6,0x85,0xdc,0x5c,0x08,0x93,0x6d,0xe6,0xf1,0xeb,0xb9,0x7c},{0x71,0x12,0x20,0xbb,0x37,0xa6,0xd8,0x71,0xf7,0x58,0xaa,0xbd,0x30,0xfb,0xac,0x94,0x62,0x45,0xf0,0x1a,0xc3,0x4a,0x07,0x78,0x6d,0x17,0xf5,0x8d,0x69,0x3d,0x2e,0x15,0x96,0x48,0x1a,0xb0,0x7e,0xdd,0xf5,0x2d,0xe1,0x56,0xfc,0xe9,0x26,0x91,0x51,0xfe,0x5e,0x2a,0xdc,0x23,0x89,0x09,0x14,0xe6,0x17,0xa9,0x14,0x8c,0x8c,0xe8,0xe3,0x71},{0xe4,0xd0,0xa7,0x5a,0xce,0x93,0x1d,0x55,0xa2,0x3d,0xdd,0x7e,0x10,0x66,0x6d,0xc6,0x5c,0x87,0x9f,0x7a,0x52,0x5e,0x76,0x3f,0x09,0x9e,0xe5,0x8e,0x60,0x39,0x5e,0x3c,0x28,0x31,0xa4,0x12,0x39,0xfd,0xba,0xda,0xc8,0x59,0xdd,0x5b,0x26,0x78,0x8f,0x33,0xd2,0xc8,0x22,0x77,0x49,0xcf,0x34,0x61,0xbe,0x7a,0xa6,0x31,0xbe,0xe5,0xab,0xc2},{0x60,0xf5,0x52,0xbd,0xb1,0x9e,0x06,0xa3,0x94,0xad,0xe0,0x82,0x33,0x7c,0x41,0x17,0x5b,0x8a,0xbc,0x7c,0xce,0xd1,0x7e,0xfd,0x39,0x17,0xfd,0x90,0x5a,0x53,0x89,0x27,0x9f,0x27,0x7a,0x08,0xb2,0x66,0xda,0xb5,0xbf,0x3b,0x80,0xe2,0x1a,0x30,0x80,0x45,0x13,0xf3,0x4b,0x0c,0x4a,0xe9,0x0a,0x6e,0xf2,0x3e,0xa3,0x70,0x3d,0x89,0xd3,0xb2},{0x23,0x41,0x08,0x8d,0xa8,0x0b,0x6a,0xe0,0x65,0xb1,0x42,0x50,0x49,0xdd,0xd3,0xe8,0x89,0x13,0x7a,0x04,0xf0,0xd6,0x2f,0x6e,0x73,0xcd,0xdc,0x10,0xbb,0x02,0x6b,0xa2,0x25,0x58,0xa3,0x08,0x37,0x7c,0x8b,0x1f,0x4a,0x81,0x38,0x88,0xbd,0xf4,0x4f,0x24,0xe8,0xd6,0x9f,0x2f,0x13,0xeb,0x79,0x60,0x80,0x90,0x52,0x6b,0x8e,0xed,0xcb,0x77},{0x5b,0x88,0x63,0xaf,0xf9,0xe2,0x44,0x23,0xc8,0x02,0xe0,0x22,0x15,0x3d,0x2a,0xb7,0x40,0x76,0xe8,0x95,0xfd,0xa9,0xe3,0x85,0x94,0xa3,0xbb,0xce,0x61,0x19,0x0d,0xe2,0x95,0xdf,0x81,0x11,0x53,0x77,0xcd,0xf2,0xd8,0x4f,0xbf,0x19,0x6a,0x3d,0x4b,0xda,0xa4,0x56,0xa4,0xcd,0x9d,0x4f,0x52,0x53,0x7d,0xd8,0xac,0xe0,0xfb,0x9a,0x71,0x0c,0x59,0xf9,0x0b,0x03,0xf1,0x7b,0xaf,0x33,0xc3,0xe5,0x1e,0x8d,0x4f,0xbe,0x21,0xed,0x6b,0x15,0xdd,0xd2,0xeb,0x7c,0xe4,0x59,0x6c,0xf9,0x91,0xc1,0x3a,0x3a,0xb6,0x2b},{0x5e,0x54,0xe5,0x1b,0x3d,0x2c,0x00,0x80,0xdd,0xe4,0x10,0x50,0x98,0xb6,0x0e,0x3a,0xf7,0xde,0x67,0x2c,0x8e,0x7b,0xb4,0x73,0x0b,0xc7,0x12,0xb0,0x66,0x6b,0x3b,0x99,0xd9,0x33,0x78,0x5f,0x45,0xe5,0xec,0x15,0x02,0xfa,0x8b,0x86,0xfd,0xe0,0xb7,0x84,0x72,0xf2,0x68,0x5c,0xd6,0x2e,0x37,0xe9,0x49,0x32,0x2f,0xcd,0xcd,0x1e,0x99,0x0f,0x59,0xf9,0x0b,0x03,0xf1,0x7b,0xaf,0x33,0xc3,0xe5,0x1e,0x8d,0x4f,0xbe,0x21,0xed,0x6b,0x15,0xdd,0xd2,0xeb,0x7c,0xe4,0x59,0x6c,0xf9,0x91,0xc1,0x3a,0x3a,0xb6,0x2b}}, + {{0xfc,0xb9,0x4e,0x4e,0x11,0xfe,0xe1,0xc5,0xc7,0x49,0x54,0xd2,0x2f,0x13,0x34,0x7c,0x91,0x7d,0x98,0x43,0xe4,0xb7,0x48,0xea,0xe8,0x26,0xcb,0x26,0x1f,0xe4,0x99,0x10,0xb9,0x34,0xc2,0xac,0xa3,0x2c,0xbd,0x9e,0x80,0xd4,0x12,0x3b,0xb3,0xf0,0x01,0xae,0x91,0x9f,0xba,0x77,0x32,0x4d,0x9d,0xac,0x1f,0x8d,0xad,0xa7,0x46,0x44,0x85,0xfb},{0x65,0x05,0x0b,0xd2,0x41,0xd3,0x58,0x2a,0x14,0xbc,0x7b,0x15,0x4a,0x6a,0x6a,0x18,0x71,0x09,0x25,0x33,0xac,0x73,0x53,0xab,0xd9,0x0d,0x8d,0xdf,0x95,0x59,0x7e,0x02,0x4c,0x03,0x11,0x5c,0xdc,0x80,0x19,0xd5,0x13,0x66,0x7f,0xf7,0xd7,0x23,0x18,0x40,0x84,0x16,0x6b,0x52,0x82,0x96,0x05,0x1b,0xfa,0xcb,0x4b,0x77,0x00,0x12,0xa0,0x28},{0x13,0xe0,0x16,0x1e,0x24,0x24,0xe9,0xde,0x9c,0x86,0xa9,0xcf,0x02,0x96,0xdf,0x8c,0x64,0xcb,0x3d,0x7d,0x8a,0x2a,0x73,0x18,0x20,0xc8,0xb0,0xac,0x10,0xa0,0x52,0x0c,0x6c,0x17,0xd9,0xbd,0x3c,0x3e,0xe5,0x0c,0x4a,0xdb,0x59,0xcc,0x59,0x15,0x08,0x1e,0xfe,0xaa,0xe3,0xd6,0xa1,0x37,0xd6,0xd5,0x6d,0x8e,0xcd,0x57,0xa9,0x81,0xb3,0x43},{0x46,0x28,0x2b,0xa0,0xe5,0xe3,0xf0,0x72,0xa7,0xbc,0x8d,0xec,0x45,0x31,0x6e,0xdb,0xb2,0x4b,0x20,0xbf,0x64,0x74,0x26,0x70,0x9b,0xd6,0xd3,0x7f,0x9f,0xc1,0x59,0x03,0x2d,0xda,0x6f,0xaa,0x7c,0x92,0xc6,0xe0,0xe8,0xaa,0x1e,0x26,0xf0,0x1e,0xcc,0xef,0x6d,0x87,0x04,0x3c,0xed,0x52,0x15,0xb3,0x9f,0x01,0x4e,0xe3,0x3c,0xb6,0xbb,0xac},{0x86,0x1a,0x25,0x8e,0x41,0x85,0xf9,0xba,0x98,0x15,0xb1,0xec,0x50,0xb4,0xd0,0xab,0x55,0x54,0xbb,0x3b,0x61,0xfc,0x54,0xf3,0x09,0xea,0xaa,0x6e,0xbf,0x03,0xc3,0x58,0x1d,0x24,0xb5,0xd5,0x45,0x5a,0x7a,0x14,0xc3,0x6a,0xa9,0xd8,0x6f,0x41,0xc3,0xb4,0x9a,0x05,0x71,0xbc,0x23,0x67,0xc2,0xa8,0xf5,0x7b,0x69,0xa5,0xe1,0x7a,0x35,0x1d},{0x3b,0xf5,0xa8,0xc0,0x2a,0x7d,0x85,0x88,0xd4,0xf4,0x26,0xd3,0xf4,0xe3,0x52,0x35,0x37,0x06,0x1e,0x71,0xc2,0x3b,0x7b,0xeb,0xf0,0x07,0x30,0x6b,0x37,0x31,0xb9,0x27,0xd8,0x0b,0x17,0xae,0xff,0xd4,0x7c,0x59,0xd7,0x2d,0xea,0xcb,0x92,0x2f,0x93,0xc7,0xd7,0xc3,0xaf,0x75,0x73,0x6a,0x3f,0x89,0xe5,0x13,0x0c,0x28,0x47,0xf4,0xa4,0x07,0xfb,0xd9,0x77,0xb4,0x1e,0xb2,0x70,0xca,0x85,0x22,0x58,0xc6,0x0b,0x19,0xc2,0xa5,0xba,0xc3,0xc9,0xb6,0x4a,0xdb,0x7d,0x4d,0x66,0xde,0xeb,0x8c,0x1a,0x23,0xb8,0x4c},{0x8c,0x57,0x0e,0x9f,0x0a,0xb2,0xf4,0x07,0xdd,0x7b,0x46,0xf8,0xa0,0xb1,0x33,0x4c,0x2b,0x1e,0x1a,0xe0,0x28,0x17,0x14,0xba,0x14,0x06,0x40,0x1f,0x30,0x0a,0x19,0xcd,0xe7,0xca,0xfb,0xdb,0xb9,0x76,0xf8,0x8a,0x81,0x3d,0x03,0x86,0x7e,0x66,0x75,0x1d,0xec,0xff,0x6b,0xa7,0xea,0x4c,0x8c,0x60,0xd2,0x1f,0x72,0x11,0x4c,0x5d,0xeb,0x01,0xfb,0xd9,0x77,0xb4,0x1e,0xb2,0x70,0xca,0x85,0x22,0x58,0xc6,0x0b,0x19,0xc2,0xa5,0xba,0xc3,0xc9,0xb6,0x4a,0xdb,0x7d,0x4d,0x66,0xde,0xeb,0x8c,0x1a,0x23,0xb8,0x4c}}, + {{0x05,0x64,0x16,0x53,0xbb,0xb2,0x6e,0x81,0xfc,0xe6,0xec,0xc8,0x0c,0xc1,0x75,0x59,0x23,0xe2,0x4b,0xd8,0x6a,0x70,0x34,0x50,0x37,0xc6,0xc2,0xbd,0x27,0xfd,0xad,0x4c,0xee,0xe4,0xf7,0xfc,0x91,0x05,0x48,0x3c,0xd4,0x09,0x78,0x00,0xce,0x15,0x37,0xdc,0xe7,0xce,0x48,0x09,0x3e,0x7f,0x01,0x9b,0x03,0xc8,0x2f,0x9b,0xe6,0x42,0xe1,0x71},{0x64,0xbf,0x63,0x91,0xe5,0x3e,0x90,0x89,0x96,0xea,0x59,0x51,0x60,0x7b,0x5f,0xfe,0x0f,0x76,0x86,0x19,0x45,0x82,0xd9,0x5e,0x1a,0xd1,0xf6,0x04,0xc6,0xaa,0x71,0xda,0x80,0xed,0x75,0x51,0xc8,0x9a,0x27,0x09,0xc3,0x50,0xe4,0x14,0xa1,0xc3,0xf8,0x3a,0x6c,0x84,0xff,0x87,0xd5,0xf0,0xb0,0x3c,0x5a,0x57,0x14,0x90,0xc7,0x31,0xf8,0x47},{0x88,0x7d,0xcc,0x81,0x2b,0xbb,0x7e,0x96,0xbe,0x78,0xe1,0xb1,0xf2,0xed,0x6f,0xd8,0xff,0xbd,0x7f,0x8e,0xe5,0xeb,0x7f,0x7b,0xca,0xaf,0x9b,0x08,0x1a,0x77,0x69,0x1d,0xc2,0xa4,0x7c,0x4d,0xa6,0x74,0x8e,0x33,0x24,0xff,0x43,0xe1,0x8c,0x59,0xae,0x5f,0x95,0xa4,0x35,0x9e,0x61,0xb8,0xcc,0x4c,0x87,0xb9,0x76,0x53,0x20,0xa3,0xf3,0xf5},{0x13,0x2a,0xcc,0x07,0xb1,0x5f,0xc7,0xf1,0x08,0x0e,0x7d,0x7e,0x26,0x56,0xd8,0x16,0x9c,0xae,0xac,0xc4,0xf5,0x9c,0x15,0x67,0xae,0xc4,0xcc,0x3f,0xc0,0xaf,0x53,0x28,0x1f,0x65,0x14,0xe5,0x7f,0x0c,0xf5,0x7a,0xe3,0x93,0xc1,0xa3,0xd1,0x4a,0x09,0x7d,0x24,0xab,0x22,0xc4,0xc4,0xce,0x85,0x37,0x86,0xa8,0x9c,0x39,0x33,0xba,0x1b,0x83},{0x6d,0x3e,0x92,0x5a,0xa8,0xfa,0xe6,0x71,0x98,0xa8,0x82,0x38,0xcc,0xed,0xd6,0x92,0x7e,0x3e,0xcb,0xb2,0x82,0x92,0x7a,0x56,0x9e,0xd6,0x29,0x45,0x42,0x04,0x76,0x82,0xa5,0xfc,0xd9,0x0c,0x12,0x4c,0x98,0x04,0x2a,0x3a,0x98,0x01,0xb8,0x62,0xe8,0xe6,0x7c,0x51,0xe3,0x7d,0x97,0xf5,0x45,0xb4,0x13,0xdf,0x15,0x68,0xc3,0x00,0x75,0x40},{0x7e,0x89,0x3d,0x7c,0x78,0x36,0x3c,0x85,0xda,0xb6,0x9b,0x6d,0xbc,0x52,0x7d,0xc6,0xaa,0xfd,0x90,0x62,0xe4,0xc4,0x1a,0x5a,0x2e,0xa1,0x57,0xd7,0xda,0x57,0xf4,0x58,0xc5,0x23,0x61,0x21,0xe1,0x93,0xfa,0x06,0x22,0xed,0x41,0x66,0x24,0x47,0xb9,0xed,0xc8,0x84,0x25,0x28,0x39,0xec,0xfb,0x29,0xa1,0xcd,0xe1,0x9d,0x02,0x48,0x6f,0x0a,0xe2,0x9f,0x98,0xfd,0x3d,0x18,0xa1,0x24,0x9c,0xc6,0x75,0xb8,0x99,0x76,0x2a,0xa4,0x9e,0xb1,0x97,0x2d,0x1c,0x99,0x65,0x5f,0x1f,0xda,0x14,0x4f,0x10,0x49,0xf1,0x7a},{0x2c,0xec,0x27,0x63,0xd2,0x77,0x14,0x2d,0x01,0x18,0x10,0xe0,0x23,0x1b,0xa2,0x25,0x61,0xd4,0x52,0xd9,0x90,0xde,0x97,0x7e,0xb8,0xfa,0x38,0x25,0xf2,0x91,0x07,0x3e,0xc4,0xa9,0x3e,0xb5,0x67,0x02,0x28,0x94,0x5c,0x34,0xa1,0x0a,0x5c,0x54,0x53,0xd9,0xb4,0xc4,0x5a,0x8e,0x57,0x18,0xc3,0x35,0xea,0x47,0x75,0xe0,0x44,0x01,0x71,0x09,0xe2,0x9f,0x98,0xfd,0x3d,0x18,0xa1,0x24,0x9c,0xc6,0x75,0xb8,0x99,0x76,0x2a,0xa4,0x9e,0xb1,0x97,0x2d,0x1c,0x99,0x65,0x5f,0x1f,0xda,0x14,0x4f,0x10,0x49,0xf1,0x7a}}, + {{0x41,0x10,0xd9,0x7f,0xb8,0x83,0x9e,0x42,0x43,0x7a,0xb0,0x6d,0xa6,0xcf,0xa5,0x7a,0x50,0x93,0x2d,0x13,0x94,0x37,0xa8,0x92,0x26,0x1f,0xad,0xe0,0x25,0x19,0x91,0x62,0x28,0xfb,0x18,0xbf,0x89,0xb0,0x42,0x80,0x14,0xcd,0xd2,0x72,0x84,0x1c,0xfd,0xe5,0xc3,0x71,0x3c,0x3f,0x12,0x5e,0xdd,0x53,0x39,0xf6,0x4b,0x9f,0xb3,0x5c,0xe3,0x15},{0xd0,0xc7,0x18,0x4d,0x68,0x9f,0xdd,0xec,0x81,0xf8,0xc6,0x0e,0x83,0x43,0x23,0x3d,0xfc,0xf3,0x66,0x55,0xa8,0x65,0x8b,0xd7,0x9b,0x3c,0x74,0x23,0xcd,0xae,0x60,0xe7,0x61,0xed,0x2c,0x7e,0xe7,0xa7,0x63,0x7d,0x72,0x47,0x6a,0x33,0x1c,0xaa,0x81,0xba,0x6f,0xd4,0x00,0xe7,0xa9,0x58,0xb2,0xad,0xee,0x3f,0x9c,0x70,0xff,0x2f,0x13,0x6f},{0x56,0x7b,0x19,0x66,0x42,0x9a,0x99,0x51,0x23,0x4f,0xb6,0xe7,0xcf,0x98,0xff,0x20,0x5a,0xc3,0x0e,0x36,0xc9,0xc6,0x20,0x25,0x0c,0x56,0x98,0xfb,0xbd,0xd6,0x66,0x4f,0x6f,0x94,0x85,0x8a,0x35,0xf3,0x50,0xad,0x87,0xde,0x95,0x9e,0xae,0x2a,0xd8,0xdd,0x78,0x87,0x96,0x2b,0xe0,0x12,0x95,0xd9,0x3b,0xb2,0x2a,0x06,0xe2,0xf0,0x06,0xd4},{0x42,0x24,0xdd,0x0a,0xd1,0x11,0x31,0x7e,0x56,0x45,0xb0,0x0e,0x86,0xc1,0x5d,0x8c,0x03,0x01,0xb8,0x33,0x20,0xbd,0x08,0x10,0xe5,0x70,0x92,0x2b,0x5b,0x86,0xd3,0x50,0x4c,0x1e,0xe3,0xd1,0x2a,0x4e,0x40,0x02,0x19,0x0b,0xf6,0x91,0xd9,0x9e,0xaa,0x54,0x7c,0x3d,0xba,0xc5,0x5a,0x9e,0xb2,0xbb,0x4e,0x0d,0x5b,0xdd,0x90,0xc9,0x7b,0xc2},{0x54,0x95,0xd5,0xdc,0x7e,0x7e,0xec,0xd4,0x67,0x08,0xdc,0x58,0xa9,0x80,0x8a,0x03,0x6a,0xf8,0x40,0xca,0x0d,0x5b,0x6c,0xe4,0xc9,0x71,0xa5,0xaf,0x2a,0xaa,0xe8,0x95,0x45,0xe7,0xe2,0xc3,0x47,0x84,0xc6,0xbe,0xe5,0x65,0xaf,0xcd,0x7c,0x20,0x5f,0x8b,0x19,0x61,0xe4,0xc9,0xc1,0x86,0xa5,0x6f,0x96,0xf3,0x9c,0x13,0x28,0x1b,0xcf,0x07},{0xc4,0x7f,0xf2,0x6f,0xcc,0x4a,0xf8,0xa4,0x1f,0x1d,0x6e,0x5e,0x30,0xb2,0x99,0x8f,0x5d,0x7c,0x26,0x1c,0x52,0x6f,0xd0,0x33,0xa7,0xf8,0xca,0x2a,0xc3,0x8c,0xa8,0xd1,0x50,0x4f,0xa7,0xe8,0xf2,0x10,0x4c,0xcd,0x8a,0x31,0x03,0xc8,0x93,0x2c,0xd7,0xe4,0x21,0xdb,0xa2,0x62,0x7b,0x1f,0x28,0x14,0x69,0x7e,0x87,0xac,0xf9,0xb4,0x97,0x00,0x62,0x86,0x14,0xd7,0xe4,0x65,0xdd,0x9e,0x1c,0x64,0x5f,0x3e,0xef,0xfe,0xa6,0x60,0x68,0x91,0x94,0x8a,0x1c,0x89,0xae,0xe4,0xcf,0x3a,0xdd,0xc0,0xb4,0x47,0xe8,0x8f},{0x12,0x80,0x00,0xda,0xce,0xc4,0x80,0x8f,0xa9,0xa1,0x5d,0x98,0x7d,0x2c,0xb2,0x9c,0x71,0xde,0x62,0x89,0x6a,0xe1,0x92,0xd7,0x96,0xdc,0xcd,0xc8,0x08,0x0e,0x48,0xbf,0x2a,0x53,0x72,0x90,0x31,0x71,0x49,0x02,0xda,0x4e,0x19,0x05,0x10,0xcb,0x41,0x97,0x44,0xdc,0x2d,0x1e,0x48,0xe5,0x0e,0x41,0x9d,0x7d,0x03,0xa3,0xe2,0x65,0xd4,0x01,0x62,0x86,0x14,0xd7,0xe4,0x65,0xdd,0x9e,0x1c,0x64,0x5f,0x3e,0xef,0xfe,0xa6,0x60,0x68,0x91,0x94,0x8a,0x1c,0x89,0xae,0xe4,0xcf,0x3a,0xdd,0xc0,0xb4,0x47,0xe8,0x8f}}, + {{0x00,0x4b,0x0b,0xf5,0x1f,0x07,0x1e,0x23,0xe3,0x93,0x7b,0x31,0x41,0x2a,0x0a,0x50,0x35,0xe2,0xbb,0xfe,0x51,0x77,0x6c,0xc9,0xc5,0x13,0xb9,0x87,0x79,0x65,0x68,0x20,0xcc,0x09,0x90,0xa9,0xe4,0xef,0x9f,0x1a,0xe1,0x69,0x76,0x14,0x82,0x42,0x88,0x4b,0xdc,0xe0,0x10,0x22,0xe2,0xd6,0x36,0x7c,0x0b,0xd9,0x08,0xea,0xfa,0xe4,0xfd,0x45},{0x57,0x5c,0x1e,0x20,0xb4,0xae,0x9e,0x9d,0x04,0xfb,0x1a,0xd7,0x23,0xd8,0x8a,0x6b,0x1b,0xb2,0xef,0xa9,0x06,0x38,0xbb,0x9b,0x43,0x2e,0xf1,0x81,0x0b,0x76,0xec,0x20,0x46,0x1b,0xc4,0x71,0x19,0x3e,0x79,0xe8,0xcf,0xea,0xdc,0x4b,0x3f,0x0b,0xeb,0x05,0x13,0x1a,0x2c,0xfe,0x16,0xe9,0xf0,0xc4,0x9c,0x41,0xab,0x45,0x1b,0xba,0x05,0xec},{0x06,0x0b,0x73,0xec,0x30,0x74,0x0d,0x8d,0x13,0x4b,0xef,0xac,0x3b,0x05,0xb6,0xed,0x2b,0x05,0xd1,0xa7,0x65,0xb0,0xcb,0x69,0x00,0xeb,0x47,0xe3,0x1c,0x07,0x8b,0x15,0xbf,0x69,0xff,0x27,0xb4,0xdb,0x77,0xaf,0xe9,0x9a,0xfb,0xb2,0x28,0xa4,0xf9,0x05,0xe4,0x3c,0x66,0x56,0x00,0x1a,0x2c,0x41,0xf2,0xe1,0x11,0x09,0xfa,0xe1,0x50,0x49},{0xbc,0x4d,0x6f,0x75,0x79,0x77,0x64,0x6b,0xec,0xac,0x1a,0x26,0x73,0x9c,0xf3,0xf1,0x4d,0x79,0xbe,0x6f,0x0c,0x07,0x22,0xd1,0xa1,0x31,0x75,0xa8,0x9c,0xb6,0x00,0x63,0x0d,0x40,0x17,0xec,0x83,0xda,0x82,0x2c,0x3b,0xfd,0x90,0xe3,0xbc,0xc2,0x2c,0xf5,0x3e,0x41,0xe9,0x98,0x57,0xa2,0xb7,0xce,0x5f,0x31,0xbb,0x0b,0x05,0x61,0x0f,0x55},{0xb7,0xab,0xb2,0x84,0xf1,0x67,0x24,0x16,0x61,0xe9,0x20,0x33,0x0b,0xff,0x22,0x61,0x70,0xa0,0x5d,0xf6,0xa8,0x33,0xc9,0x30,0x73,0xe5,0x89,0x36,0x59,0xea,0xa8,0xe7,0x03,0xf6,0x14,0xc1,0x79,0xb6,0x42,0xa5,0xc8,0x6c,0xb8,0x94,0x29,0x24,0x00,0x09,0xb5,0x54,0x3f,0xe1,0x6b,0xfb,0x4d,0x2d,0xa9,0x9a,0x02,0xa1,0xa5,0x09,0xf4,0xcb},{0x92,0xfa,0x18,0x84,0x3e,0xdb,0xdf,0x7d,0x87,0xd6,0x2d,0x07,0x05,0x2c,0xba,0xe4,0x30,0x76,0xa2,0xe8,0x71,0x3b,0x1b,0x93,0x5b,0xce,0x2e,0xec,0x50,0x6e,0x4a,0x0b,0x2d,0xbe,0xa3,0x76,0x92,0xf8,0xc8,0x4a,0x71,0x66,0xec,0xfa,0x36,0xc5,0xdb,0xab,0x99,0x9c,0xbf,0x99,0x07,0xe8,0xfe,0xf4,0x2f,0x90,0x16,0x5d,0xdc,0xbe,0xfa,0x08,0x93,0xde,0x13,0xf5,0x32,0x45,0x9a,0xde,0xa2,0x5d,0xb9,0xe0,0x38,0x4c,0x6a,0xcc,0x13,0x46,0x27,0x28,0xbf,0xf8,0x7a,0x9c,0x2e,0xde,0x6f,0xfe,0xe1,0x86,0x41,0x79},{0xa7,0x32,0x52,0x76,0x4f,0x3e,0x1b,0xab,0x82,0x18,0x14,0xe7,0x42,0x32,0xb8,0xa4,0x98,0xde,0xa4,0xd7,0xae,0x42,0x84,0xda,0x71,0xf7,0x78,0x40,0x56,0x94,0x64,0x49,0x34,0x37,0xeb,0xe3,0x05,0x4c,0xb9,0xbb,0xce,0xb2,0x72,0xc0,0x75,0x1c,0xc4,0xd5,0x1e,0x3a,0xc1,0x43,0xda,0xd1,0x81,0x82,0xa9,0xd5,0x0e,0x0a,0x5e,0xc2,0xd7,0x04,0x93,0xde,0x13,0xf5,0x32,0x45,0x9a,0xde,0xa2,0x5d,0xb9,0xe0,0x38,0x4c,0x6a,0xcc,0x13,0x46,0x27,0x28,0xbf,0xf8,0x7a,0x9c,0x2e,0xde,0x6f,0xfe,0xe1,0x86,0x41,0x79}}, + {{0xa3,0xdf,0x4a,0xfd,0xe6,0x74,0xb8,0xeb,0xed,0xe7,0x7e,0xd2,0xae,0xf8,0x40,0x80,0x3a,0x55,0x58,0x1d,0x6b,0xa4,0x32,0x6c,0x15,0xbb,0x67,0xdf,0x9e,0xb5,0x70,0x4b,0x7f,0x4d,0xfe,0x34,0x42,0x0c,0x4d,0xe3,0x97,0x87,0x6d,0x08,0xe8,0x4d,0x8a,0xa9,0xbc,0xbf,0x1b,0xb7,0x66,0x32,0xf4,0x7f,0x93,0xca,0xa4,0xd2,0x8f,0x02,0x7b,0xfa},{0xea,0xac,0xdf,0x25,0x39,0xf3,0x28,0xb6,0xbe,0xa8,0x4a,0x32,0x59,0x4b,0x4f,0xb5,0xd2,0xf7,0xf5,0x75,0x43,0x8b,0xb3,0x6a,0x98,0x8c,0x14,0xc9,0x3f,0x7e,0x5c,0x05,0xf0,0xeb,0x1d,0xc5,0xe6,0x1b,0x5d,0x7f,0x38,0x5d,0x9a,0xbe,0xc8,0x97,0x09,0x65,0x62,0x88,0x99,0xda,0x95,0x13,0x93,0xd9,0xa3,0x19,0x0a,0xa7,0x4a,0xb2,0x81,0xa4},{0x6e,0x70,0x65,0xaa,0x1b,0x16,0xcb,0xc1,0x59,0x6b,0xc9,0x4d,0xd1,0x0a,0x9d,0x8c,0x76,0x70,0x3c,0xc1,0xc1,0x66,0xa6,0x9f,0xfc,0xca,0xb0,0x3f,0x0e,0xe9,0xa9,0x36,0x09,0x4f,0x94,0xf3,0x32,0x25,0x34,0xf6,0xe4,0xf9,0x0b,0x0c,0xe6,0xe0,0x6d,0x9e,0xa5,0x52,0x82,0x9c,0xd4,0x43,0xa4,0xd1,0xd1,0x63,0x20,0xce,0xbc,0x4f,0x43,0xdc},{0x35,0xd6,0xc1,0x68,0xa6,0xd7,0xd3,0x36,0x82,0x2a,0x0f,0x29,0x3e,0xd6,0x15,0x29,0x19,0x73,0x14,0x78,0x87,0x86,0xca,0x9f,0x6e,0x17,0xea,0xaf,0x24,0x37,0xd6,0xb4,0xb0,0xee,0x84,0x90,0x2d,0x18,0xbd,0x26,0xc3,0xd4,0x39,0x4f,0x45,0xfa,0x2f,0x70,0xf2,0xe2,0x2a,0x2a,0x5c,0x65,0x15,0xcb,0xaf,0x92,0x9a,0xfc,0x06,0xe0,0x8a,0x1b},{0x5d,0xfa,0xc0,0x2b,0xc3,0x94,0x19,0xb4,0xd6,0x13,0xe3,0xcf,0x91,0xad,0x8c,0xe1,0x97,0x46,0xfe,0xea,0x74,0xe0,0x0c,0x03,0xf7,0x2e,0x51,0xa7,0xf2,0xbc,0xce,0xe8,0x6b,0xfd,0x2f,0x54,0x52,0x12,0x00,0x8d,0x95,0x91,0xc3,0xf6,0x25,0xf8,0x65,0x6a,0x9c,0x79,0x6b,0x71,0xc0,0x0c,0x29,0xfb,0xe7,0x14,0x9f,0x2f,0x1a,0x07,0x53,0x50},{0xe9,0xd4,0x46,0x0b,0x51,0x3f,0xf1,0xbe,0x0a,0x23,0xa5,0x38,0xa0,0xe3,0x70,0x14,0x63,0xf0,0x94,0xbb,0x1c,0x4f,0x23,0x05,0x1b,0x62,0x40,0x9b,0xf9,0x52,0x1b,0x41,0x51,0x57,0x2a,0x99,0x73,0xda,0xe1,0xcf,0xc5,0x4c,0x65,0x3a,0xc2,0x9d,0x73,0xda,0xc9,0x59,0xf1,0xdf,0xab,0x2b,0x27,0xe1,0x59,0x8b,0xa7,0x48,0xf9,0x36,0xcb,0x08,0xe3,0x5e,0x1d,0xdd,0xf9,0x20,0x4f,0x64,0xa9,0x26,0x74,0x97,0xf2,0x2d,0x31,0xac,0x8c,0x20,0x77,0x09,0xa9,0x8f,0xed,0x23,0x77,0x7e,0xd7,0x34,0x93,0x84,0xe7,0xaa},{0xaa,0xf7,0x64,0xdf,0x34,0x59,0x1c,0x2c,0xbc,0x47,0x08,0x6a,0x25,0xbf,0x9d,0x48,0x54,0xcf,0xa0,0x6c,0xfc,0xd4,0x10,0x39,0x9f,0x64,0x46,0xce,0xd9,0x95,0x28,0x89,0xdf,0x94,0x5e,0x74,0x0b,0x55,0x46,0x82,0xd9,0x3d,0x82,0x97,0x7d,0xd0,0x3e,0xd7,0xf6,0x6f,0xaa,0x97,0x3e,0xdf,0xa7,0xde,0xe3,0xc5,0xaf,0xd3,0xa0,0x5a,0x30,0x0d,0xe3,0x5e,0x1d,0xdd,0xf9,0x20,0x4f,0x64,0xa9,0x26,0x74,0x97,0xf2,0x2d,0x31,0xac,0x8c,0x20,0x77,0x09,0xa9,0x8f,0xed,0x23,0x77,0x7e,0xd7,0x34,0x93,0x84,0xe7,0xaa}}, + {{0x96,0x4e,0xf2,0x1e,0x3a,0xe5,0x77,0xbf,0xa7,0x1c,0x3d,0x66,0x08,0x06,0xca,0x55,0x43,0x7a,0x08,0xf8,0xff,0x55,0xb3,0xbc,0x9a,0x83,0x9a,0x2e,0xe6,0x97,0x14,0x32,0x36,0x57,0x5c,0xa4,0x04,0x78,0xb1,0x92,0xf4,0x23,0x94,0xe6,0x2a,0xef,0xd4,0xe7,0xc4,0x02,0x9f,0xa9,0x79,0x77,0x61,0x90,0xd6,0xdb,0x6e,0x28,0x7e,0xc0,0x1d,0x70},{0xc5,0xd1,0x5c,0x34,0x15,0xa9,0x1e,0x42,0x2a,0x1b,0x0d,0xf0,0x56,0x83,0x10,0xc3,0xc9,0x21,0xfd,0x05,0xfa,0x51,0x0e,0x11,0x28,0xcc,0x84,0xac,0x35,0xb5,0xd8,0xc8,0x5c,0x80,0x11,0x1f,0x60,0x1c,0x72,0x25,0x82,0x45,0xb5,0x4f,0x66,0x6b,0x52,0xb1,0xf7,0x28,0x0f,0x80,0x76,0x44,0xdc,0x15,0x70,0x39,0xe9,0xaf,0xc7,0x0a,0xa0,0x43},{0xff,0x20,0x5e,0x3b,0x75,0xe9,0x38,0x7c,0xa3,0x5c,0x8b,0x1a,0xec,0x17,0x8d,0xf0,0xef,0xb3,0x53,0x9b,0x16,0xa9,0x44,0xf9,0x34,0x45,0x13,0x66,0x80,0x24,0xdc,0x22,0x0e,0x51,0x94,0xed,0xe6,0x83,0x36,0x32,0x63,0x23,0x1b,0xf8,0x78,0xb4,0x04,0x7f,0x5a,0x50,0x54,0x12,0x19,0x04,0x61,0xdd,0x25,0xf0,0x48,0x29,0x04,0xc1,0x44,0xe2},{0x46,0x32,0x2d,0xc7,0xbc,0x05,0x2a,0xd3,0xb5,0xce,0x7d,0x47,0x5e,0xfc,0x90,0x38,0xef,0xfa,0x6f,0x42,0xf0,0x66,0x05,0x89,0x7c,0x9a,0xc1,0xfd,0xa2,0xe8,0xa7,0x38,0x18,0x6d,0x7f,0x9e,0xfb,0xbd,0x06,0x0c,0x70,0xd7,0x29,0x10,0x88,0x04,0x9f,0x24,0x28,0x9d,0xc7,0x84,0xdf,0xb6,0xec,0xb2,0xc7,0x1b,0xd1,0xc1,0x9d,0x56,0xb0,0x83},{0xda,0xd7,0x34,0xee,0x62,0x13,0x8f,0x47,0xad,0xb4,0x9c,0x98,0xe4,0xc5,0xb3,0x29,0x31,0x11,0x64,0xad,0xf5,0x0b,0x60,0xe1,0x0e,0x18,0x28,0x30,0x3c,0xa2,0xe3,0x29,0x89,0x0a,0x7e,0x18,0xba,0x30,0x9e,0x7d,0x53,0xf1,0x82,0xd5,0x27,0xe5,0xf3,0xab,0x15,0xcd,0x62,0x7e,0xdf,0xf0,0x0e,0x42,0xfa,0x6b,0x7b,0x54,0xd2,0x74,0x19,0x8f},{0x29,0x4d,0x28,0x80,0x62,0xb5,0x77,0xbb,0x69,0x70,0xb0,0xb7,0x10,0x2e,0xed,0xfc,0x13,0x34,0x93,0x7f,0xd8,0xfc,0xb5,0x7b,0xfe,0x34,0x0a,0xa3,0x95,0x5b,0xb1,0xa7,0xc6,0xab,0x82,0x79,0x25,0x23,0x94,0x12,0xa4,0x34,0xec,0x23,0xca,0xcb,0xd0,0xa3,0xf9,0x31,0x32,0xce,0x50,0x31,0x73,0x23,0x98,0x94,0xe3,0x08,0xd9,0x1e,0xc3,0x0b,0x39,0xe3,0x3b,0xf2,0xe8,0xb7,0x26,0x28,0x9d,0xb3,0x12,0x8d,0x16,0xca,0x89,0x26,0xa9,0x1c,0xa3,0x1f,0x36,0x10,0x60,0x6a,0x29,0x85,0xe7,0x2c,0xee,0xc1,0xb6,0xae},{0x68,0xed,0x3c,0x64,0xe6,0x87,0xf0,0x14,0x64,0xfc,0x38,0x3a,0x0f,0xd9,0x7a,0x5b,0x52,0x32,0x10,0xca,0xc6,0x83,0x0b,0xae,0x17,0x0e,0xfe,0x77,0xe0,0xe7,0x83,0xa1,0x2c,0x78,0x62,0x9c,0x79,0x08,0x2b,0xd4,0x85,0x72,0x27,0x8d,0x97,0x78,0x62,0x33,0x34,0xeb,0x5c,0xde,0x5d,0xaa,0x4d,0xfa,0xd1,0x67,0xa4,0xea,0x45,0xad,0xf9,0x06,0x39,0xe3,0x3b,0xf2,0xe8,0xb7,0x26,0x28,0x9d,0xb3,0x12,0x8d,0x16,0xca,0x89,0x26,0xa9,0x1c,0xa3,0x1f,0x36,0x10,0x60,0x6a,0x29,0x85,0xe7,0x2c,0xee,0xc1,0xb6,0xae}}, + {{0xd9,0x64,0xb2,0xe1,0x9f,0x0a,0x35,0xfc,0x9f,0xc3,0xa5,0x2a,0xa3,0x84,0xb4,0xf3,0x23,0xc4,0xf3,0x5a,0x9d,0xf8,0x7f,0x35,0xa9,0xf5,0x5b,0x68,0xfc,0x19,0x69,0x63,0x6a,0x13,0x19,0x32,0xcc,0x9d,0x0c,0x3c,0x7d,0xdd,0x85,0x16,0xa8,0xd9,0x2b,0x75,0x08,0x4b,0x9a,0xa5,0x6e,0xf3,0xe9,0xeb,0xed,0x5d,0x2e,0xfd,0x2e,0x0c,0x60,0xa2},{0x0f,0xf6,0x8c,0x3f,0x6e,0xee,0x56,0x4f,0x43,0x6f,0x54,0xbd,0x7a,0xe4,0xbe,0xa8,0x77,0x05,0x99,0xe7,0x9e,0x59,0x22,0x85,0x9b,0xc6,0xe4,0x2a,0x61,0x9c,0x19,0xb1,0x5a,0xeb,0x7a,0xf8,0x41,0x4e,0xe5,0x2a,0xd0,0xf7,0x44,0xf0,0x16,0xea,0x0c,0x04,0x19,0x6c,0xb6,0x30,0x3c,0x6e,0x2d,0x79,0x9a,0x8f,0x08,0x90,0x11,0xf1,0xc0,0x4d},{0x68,0xe7,0x1d,0x40,0xf1,0x07,0xc0,0xc6,0xb2,0x87,0x9c,0xa2,0x19,0x43,0x7a,0xdf,0x8a,0x5a,0x0f,0xe2,0x24,0x97,0xa0,0x38,0x79,0x20,0x38,0xa9,0x9c,0x77,0xc4,0x37,0xa6,0x02,0xe0,0x93,0x47,0xa4,0x55,0x21,0xc2,0x69,0xbe,0x09,0x05,0xaa,0x87,0x28,0xf1,0x95,0x2f,0xdb,0xf0,0xbf,0xd2,0x9e,0x5e,0x3a,0xfa,0xc6,0x2f,0x13,0x09,0xaf},{0xe1,0x9e,0xc8,0x4f,0xc9,0xdd,0x61,0x60,0x94,0xbc,0xd3,0xd6,0xde,0x11,0x6e,0xec,0x84,0xc4,0xdd,0xbe,0x20,0x46,0x6c,0xef,0xf6,0x9d,0x37,0x07,0x53,0x72,0x57,0xf9,0x02,0xb5,0x64,0x1f,0xe2,0x56,0xa4,0x38,0x6d,0xa4,0xed,0x23,0x9e,0xa3,0xf4,0x4d,0x77,0x52,0xdc,0x8c,0x51,0xfc,0x88,0x18,0xbc,0x83,0x2a,0xac,0xc1,0x1d,0x3d,0x59},{0x08,0x4f,0x78,0x21,0xfd,0x4b,0x85,0x86,0x4e,0x25,0xdd,0x47,0x60,0x7f,0x7e,0xc6,0xd3,0xa1,0xab,0x91,0x3f,0xeb,0xf6,0x40,0x7e,0x1b,0xbd,0x99,0x9c,0x7c,0x2f,0x4f,0xca,0x68,0xa5,0xf6,0x8c,0x1e,0xcb,0xb8,0x76,0xe2,0x87,0x5b,0x49,0x68,0x97,0x2c,0x21,0x5c,0x7c,0x93,0x79,0x9a,0x95,0xa1,0x3a,0x49,0xc9,0x6d,0x34,0x6b,0xa1,0x98},{0xb9,0x88,0x25,0x9a,0x3b,0x53,0x56,0xa1,0x48,0x0f,0xf0,0x92,0xde,0x4e,0x3e,0x3a,0xcf,0x02,0xdc,0x5c,0xc2,0xc3,0x78,0xad,0x8a,0x0c,0x3c,0xc7,0xdd,0xdd,0x71,0x6e,0x3f,0xd9,0x3a,0x57,0x2a,0x19,0xa5,0x3b,0x5c,0x46,0x7b,0xc9,0x0f,0x16,0xb3,0x58,0xa6,0x85,0xfa,0x91,0x2c,0x9a,0x9c,0x12,0xb6,0xd6,0x7d,0x9a,0xf0,0x9d,0xe9,0x02,0xad,0x12,0x87,0xda,0x85,0x58,0x6b,0xff,0x68,0x96,0x05,0x33,0xba,0x7f,0x08,0xf9,0xa9,0xa2,0xa9,0x46,0x43,0xe5,0x03,0x12,0xe4,0xbe,0x74,0xaa,0x46,0x4e,0x51,0xb3},{0x61,0x70,0x17,0x50,0x26,0xfa,0x51,0x83,0xe0,0xca,0xa9,0xb1,0xc3,0xc4,0x83,0xa9,0xb6,0x43,0x6b,0x7a,0x5b,0xe4,0x21,0x5a,0x6b,0xd4,0x34,0xf8,0xee,0x95,0x86,0x2d,0x03,0xbf,0xca,0xd0,0xfa,0x68,0x53,0xb2,0x97,0x50,0xad,0x89,0x2f,0x99,0x63,0x67,0x18,0x57,0x1f,0x57,0x41,0xbc,0xb7,0xc0,0x18,0xe7,0xb6,0xf3,0x0f,0xc4,0x49,0x0d,0xad,0x12,0x87,0xda,0x85,0x58,0x6b,0xff,0x68,0x96,0x05,0x33,0xba,0x7f,0x08,0xf9,0xa9,0xa2,0xa9,0x46,0x43,0xe5,0x03,0x12,0xe4,0xbe,0x74,0xaa,0x46,0x4e,0x51,0xb3}}, + {{0xc5,0xdf,0x86,0x8f,0xf1,0xa7,0xad,0x57,0xfd,0xb4,0x53,0xc3,0x92,0x1b,0x9e,0x2e,0xdd,0xc5,0xa4,0x3b,0x72,0xa6,0x9b,0x4a,0x15,0xca,0x35,0xed,0x3c,0x1a,0x3b,0x38,0x36,0xd6,0xf2,0x03,0xb6,0x97,0x1f,0xcb,0x40,0x5d,0x3c,0x25,0xfc,0xe7,0xff,0xc6,0xbe,0x61,0xe1,0x98,0x31,0x13,0xa9,0xbe,0x05,0x86,0xfe,0x5c,0xf6,0xcc,0xaa,0xf5},{0xd2,0x57,0x19,0x98,0xf8,0x74,0x90,0xb7,0x69,0x6e,0xdd,0x44,0xf1,0x8b,0xb1,0x9c,0xfd,0x5b,0x6b,0xc0,0x45,0xf2,0x49,0xa5,0x4b,0xff,0x8b,0x7f,0x87,0xe3,0xf9,0x71,0xab,0xfa,0xc8,0x17,0xed,0xeb,0x19,0xc6,0x3c,0xee,0x78,0xba,0x89,0x97,0x49,0x85,0x39,0x68,0x29,0x88,0x0b,0x1c,0xd1,0x42,0x8b,0xe8,0x1a,0x3b,0xeb,0x4d,0xef,0x3b},{0xea,0xfb,0xec,0x27,0xc3,0x92,0xc3,0x68,0x0d,0x3c,0x5b,0x20,0x20,0x9c,0x96,0xa7,0x39,0xfa,0x80,0x91,0xef,0x86,0x7d,0xa8,0x87,0xf6,0xef,0x14,0x01,0x46,0xf0,0x68,0x0a,0x8b,0xae,0x83,0x91,0x7e,0xa0,0x14,0x14,0xde,0xf9,0xa8,0xfd,0x67,0x57,0x17,0x20,0x46,0x43,0x49,0x07,0xf0,0x3e,0xc8,0xbe,0x66,0xaf,0x58,0x3a,0xbd,0xd8,0x00},{0x35,0xf5,0xc8,0x2c,0x0e,0x4b,0x56,0xe0,0xef,0x08,0x34,0x38,0x57,0xe9,0xde,0xdb,0x1d,0xe1,0x28,0x05,0x01,0xed,0x62,0x3d,0xa9,0x6e,0xea,0x5b,0x95,0x09,0xe0,0x04,0x46,0xff,0xdc,0x34,0xf6,0xf7,0x63,0xb1,0x76,0xb8,0x3c,0x03,0xef,0x36,0x0f,0x82,0x1b,0x5b,0x6f,0xe2,0x86,0xd9,0x10,0x01,0xe6,0x73,0x75,0x0d,0x50,0x30,0x11,0x68},{0x27,0xb6,0x3b,0x78,0x79,0xf3,0x22,0x78,0x8f,0x0c,0x14,0x8b,0x3f,0x68,0xc2,0xab,0x9f,0x9f,0x05,0x70,0x7e,0xee,0x4b,0x1b,0x6b,0xfc,0x04,0x72,0xca,0xf1,0x9a,0xba,0xe3,0x65,0x9d,0xdb,0x01,0x33,0xc5,0xdb,0xf6,0x87,0xe4,0x73,0x5a,0x0f,0x94,0xa9,0x2e,0xfe,0x8f,0x3e,0xd1,0x0a,0x6d,0xa1,0x21,0x2a,0x92,0x8c,0x4b,0x43,0x13,0x2f},{0xa3,0xa8,0x3b,0xb4,0x4f,0x8a,0xac,0xab,0x8a,0x4c,0x39,0x7e,0xb8,0x2f,0xb1,0x01,0x2e,0xbe,0x0e,0x7d,0x28,0x8a,0x18,0x4a,0xda,0x58,0x1a,0xfb,0x95,0x97,0xf3,0x63,0x58,0xbe,0x8c,0x30,0x13,0x9b,0xba,0x9f,0x4e,0xac,0x8d,0x95,0xf2,0x07,0xbb,0x85,0xa1,0x41,0x4c,0x33,0xe3,0x58,0x8e,0x5c,0xa1,0x05,0x45,0xab,0x5c,0x0c,0xe4,0x02,0xc3,0xa0,0xa0,0x72,0xdb,0x9a,0x9d,0xbf,0x13,0x29,0x94,0x70,0x8b,0xe4,0xe8,0xdb,0x0e,0x0b,0xd0,0xa0,0x25,0xad,0x71,0xa0,0x27,0x9c,0x1d,0x77,0xb0,0x98,0xa8,0x03},{0xe1,0x84,0xa5,0xea,0xa5,0xd8,0x1b,0x29,0xce,0xd7,0xa3,0x72,0xa7,0xc9,0xa5,0xea,0xf1,0x02,0xf3,0x0c,0xb0,0x65,0x12,0xbc,0xa4,0xf2,0x5d,0x69,0x00,0xa4,0x7f,0x5a,0x52,0x09,0xb6,0x7b,0x30,0xf2,0x99,0x03,0x39,0x9d,0xee,0x6f,0xb5,0xf7,0x9e,0x7a,0x97,0x8b,0x81,0x03,0x8c,0xdd,0x35,0xfc,0x1f,0x0a,0xc6,0xa4,0x60,0x7b,0xc8,0x0a,0xc3,0xa0,0xa0,0x72,0xdb,0x9a,0x9d,0xbf,0x13,0x29,0x94,0x70,0x8b,0xe4,0xe8,0xdb,0x0e,0x0b,0xd0,0xa0,0x25,0xad,0x71,0xa0,0x27,0x9c,0x1d,0x77,0xb0,0x98,0xa8,0x03}}, + {{0x67,0xe9,0x62,0x76,0x3a,0x90,0x9b,0x6b,0x19,0x1d,0x65,0xb2,0x2a,0x2f,0xf7,0x50,0xaa,0x54,0xa5,0xbb,0x53,0xb5,0xf9,0xee,0x0c,0x04,0x3a,0x3c,0x29,0x4b,0x66,0x3e,0x7b,0xb6,0xaa,0xd2,0x10,0x89,0xcc,0x89,0x2c,0x47,0xbe,0x23,0xd6,0x52,0x81,0x5d,0xc8,0xbc,0x49,0xd6,0x6a,0xcd,0x62,0x99,0x30,0xff,0x16,0xa5,0x50,0x44,0xd8,0x7a},{0xd6,0xcd,0xfe,0xd4,0x44,0x4a,0x9e,0x90,0x44,0x73,0x8a,0xff,0xbb,0x82,0x08,0xb6,0x7f,0xf2,0x87,0xcb,0xa5,0x0b,0x56,0xd3,0x9e,0x91,0xb8,0x52,0x6b,0x25,0xa6,0x5d,0x50,0xaf,0x9b,0xd5,0xfb,0x9f,0x7e,0x2d,0x57,0xdf,0x30,0x78,0x8d,0x1a,0xc3,0xac,0x9c,0x5a,0xbf,0xab,0x5a,0x0d,0xc9,0xb6,0x4b,0x18,0xd4,0xe7,0x55,0x40,0xde,0x7e},{0xc2,0xa9,0x7e,0x5c,0x26,0xf4,0x7d,0xce,0x9e,0x73,0xae,0x50,0xde,0xe7,0xa6,0xf9,0x8b,0x57,0xf9,0x7a,0x4c,0x38,0x82,0xf6,0x30,0x80,0x12,0xf7,0xf6,0x66,0x80,0x46,0x4d,0x41,0x53,0x63,0xd9,0x65,0x90,0xe7,0xee,0x24,0x07,0xb0,0x4f,0xeb,0x3e,0x8e,0x83,0x21,0xa3,0x40,0x03,0xc0,0x64,0x52,0xc6,0xb2,0x12,0x9d,0x8d,0x86,0xdd,0x19},{0xe2,0xd5,0x49,0x5e,0x2a,0x6e,0x4e,0xd9,0x31,0x26,0x53,0x13,0x98,0x5e,0x2f,0x23,0xea,0xa0,0x30,0xee,0xef,0x62,0x2b,0xdc,0x93,0x65,0x90,0xad,0x9a,0xf1,0x74,0x12,0xf5,0x24,0x33,0xcc,0xc3,0xda,0x42,0x54,0xa6,0x6c,0x86,0x99,0xb9,0xb5,0xf7,0x07,0x90,0xd8,0x85,0x7f,0x69,0xfb,0x19,0x2a,0x2c,0xc0,0x11,0x81,0x64,0x37,0x38,0x07},{0xc7,0xb3,0xf5,0xe4,0x4b,0x55,0xcf,0xd8,0x2b,0x72,0xde,0x62,0xfc,0x66,0xea,0x82,0xee,0x2e,0xe5,0x4f,0x66,0xba,0x19,0x63,0x01,0x0b,0x2d,0x89,0xb4,0xaa,0x76,0xb3,0x7e,0xc5,0xbe,0xdd,0x57,0x90,0x5e,0xff,0x5b,0x9a,0x71,0xe1,0x47,0xf9,0xec,0xe5,0xf0,0x19,0x89,0x17,0x65,0x3e,0x56,0x4a,0x98,0xb2,0x3c,0x3b,0xf0,0x14,0x13,0x1b},{0xc0,0x72,0x26,0x96,0x6b,0xf5,0x50,0xa1,0x65,0xcd,0xfe,0x92,0xa5,0x5a,0xb3,0x56,0x27,0x5b,0x2f,0x4a,0x8f,0x67,0xaa,0xf4,0xa1,0x6e,0x3c,0x66,0xcc,0xb7,0x71,0x70,0xff,0x70,0x1f,0x9e,0x09,0xae,0x31,0xcb,0x2a,0xd5,0x8a,0x38,0xa9,0xaf,0xbc,0x94,0xa2,0xa8,0xe9,0x77,0x1c,0xc3,0xfa,0xd1,0x45,0xd2,0xe2,0xff,0x7d,0xf2,0x44,0x00,0xa0,0xc3,0xc1,0xdd,0xa0,0x4c,0xfb,0xed,0x1a,0xbd,0x0c,0x05,0x3b,0xa9,0xc8,0x98,0xb0,0x7d,0x6a,0x77,0xcb,0x08,0x70,0x64,0x31,0x9d,0x9c,0x7b,0x40,0x9e,0xbb,0xf4},{0xbc,0x88,0x9d,0x36,0xae,0xbc,0x92,0x47,0x63,0x85,0x41,0xe3,0x1e,0x1c,0x39,0xf5,0xd3,0xc2,0x0a,0x7d,0x18,0x7a,0x8f,0xd3,0x0c,0x37,0x50,0x28,0x35,0x93,0x77,0x4b,0xcb,0xba,0x35,0x4e,0x94,0x48,0xe4,0x0c,0xa7,0x36,0x4f,0x74,0x2b,0xf9,0xb5,0xb5,0xeb,0x91,0x50,0x3c,0x67,0x9b,0x4d,0x25,0xd4,0x0e,0x0d,0xb9,0x5b,0x77,0xf3,0x0e,0xa0,0xc3,0xc1,0xdd,0xa0,0x4c,0xfb,0xed,0x1a,0xbd,0x0c,0x05,0x3b,0xa9,0xc8,0x98,0xb0,0x7d,0x6a,0x77,0xcb,0x08,0x70,0x64,0x31,0x9d,0x9c,0x7b,0x40,0x9e,0xbb,0xf4}}, + {{0x44,0xdd,0x62,0x9e,0x0f,0xee,0x20,0x11,0x37,0xfc,0xd0,0x5c,0xe4,0xe1,0x0a,0xb8,0xc2,0xe0,0x9c,0x2c,0x3e,0x1b,0x31,0x1c,0xdb,0xa3,0x84,0x9a,0xb7,0x4e,0x40,0x74,0x21,0xfd,0xfc,0x65,0xbd,0x38,0x8a,0x55,0x6f,0x1e,0xc3,0x14,0xfc,0x66,0x04,0x7b,0xc4,0x61,0xb0,0xcb,0xfa,0xdd,0x50,0x45,0x4b,0x2e,0xf0,0x6d,0x0f,0x26,0x6d,0xbf},{0xe6,0xbc,0x35,0x73,0xb3,0x11,0x38,0xc6,0x31,0x82,0x96,0x80,0x1d,0xa9,0xd9,0x17,0x85,0x4e,0xad,0x0f,0x5c,0xb7,0xe8,0x78,0x62,0x2f,0x3c,0x10,0x0e,0xdc,0xf2,0x7e,0xf5,0x02,0x6d,0x1a,0x50,0xc2,0x50,0x7d,0x0d,0x14,0x77,0x77,0xfc,0xbe,0x23,0x02,0x81,0x0a,0xdc,0xa3,0x16,0xfd,0xab,0xb9,0x7c,0xb6,0x7e,0x8a,0xde,0x1f,0x22,0xeb},{0xab,0xf3,0xea,0x63,0xc0,0x25,0xa2,0xc7,0x6a,0xfe,0x91,0x4a,0x0a,0x91,0xdd,0x6d,0x6f,0x8c,0xf9,0xa8,0x1c,0x9f,0xb5,0xe5,0xd2,0xac,0xe6,0x51,0x9a,0xd3,0x87,0x17,0x82,0x12,0x0a,0x58,0x99,0x7f,0x81,0x2d,0x8d,0x27,0x2d,0x1b,0xb0,0x02,0x7e,0x0d,0xd6,0x18,0x89,0x5e,0x0c,0x2b,0x57,0xa6,0x56,0x35,0xff,0x71,0x4e,0xb0,0x49,0x38},{0x36,0xdf,0x1d,0x1c,0xf6,0xa7,0x4d,0x87,0x7e,0x2c,0x3f,0xb4,0xda,0xd7,0x80,0x71,0x0b,0xf3,0x2a,0x47,0x20,0xe6,0x9a,0x3d,0x17,0x9a,0x97,0xc9,0x4e,0x53,0xa6,0xe2,0x23,0xea,0x94,0x4d,0xf9,0xeb,0x2c,0x03,0x2c,0x88,0xa2,0xe6,0xc5,0x94,0xa5,0x6f,0xc3,0x98,0xa9,0x8b,0xa7,0x41,0x7d,0xd3,0x82,0x01,0x13,0xb6,0x0f,0x39,0x1e,0xd2},{0x08,0x28,0xc3,0x1c,0xec,0x21,0x3a,0xb4,0x4c,0xb1,0xfa,0xb9,0x0c,0xfe,0xc2,0x50,0xc5,0x99,0x62,0xa0,0x11,0x74,0xcf,0x05,0x1e,0x2b,0xdf,0x6d,0x22,0x8e,0x6e,0x55,0x19,0x21,0x9c,0xa1,0x98,0x56,0x45,0x90,0x40,0x3a,0x8e,0xad,0x76,0x4d,0xd3,0x95,0x27,0x67,0x4e,0x02,0x16,0xc3,0xfe,0x5a,0x79,0x4e,0x2d,0x6f,0xd0,0xe4,0x4f,0x62},{0x40,0x14,0xe1,0x88,0x3d,0xcc,0x51,0xcb,0x98,0x86,0x06,0x4d,0xe4,0x52,0x71,0xe2,0x2e,0x2b,0x80,0xfd,0x81,0x65,0xaf,0x93,0x31,0x87,0xe0,0xff,0x31,0xab,0xff,0x53,0x0e,0x2d,0xb1,0x47,0xe6,0x44,0xb7,0x29,0xab,0x0f,0x51,0x3a,0x53,0x84,0x36,0x58,0x8c,0x5f,0x7b,0x65,0x6a,0xb7,0x6f,0xdc,0xad,0xc1,0xa3,0xe4,0x21,0xfc,0x22,0x0e,0xc1,0x10,0xd1,0x7d,0x9f,0xd3,0x1e,0x33,0xb4,0xca,0xb9,0xff,0xd8,0x27,0xb8,0xca,0xde,0x49,0x6f,0xdc,0xf0,0xe8,0x70,0x36,0xdb,0x90,0x00,0x07,0x9e,0x77,0x39,0xfe},{0xc9,0x93,0x4b,0xe6,0x47,0x7e,0x1d,0x86,0x15,0x46,0xe8,0x27,0xf5,0x84,0x67,0x4e,0x42,0xe3,0x2b,0x8a,0x4e,0x90,0x7b,0x87,0xcc,0xdf,0xaa,0x04,0x06,0x05,0xe6,0x72,0xff,0x6f,0x44,0x1b,0x08,0xad,0x79,0x3e,0xb7,0xdd,0xd7,0x2c,0x73,0xf0,0xf0,0xc4,0x6e,0xb7,0x37,0xe1,0x02,0xf5,0x42,0xe7,0xef,0xa1,0xdd,0x50,0x9a,0xc5,0x8d,0x00,0xc1,0x10,0xd1,0x7d,0x9f,0xd3,0x1e,0x33,0xb4,0xca,0xb9,0xff,0xd8,0x27,0xb8,0xca,0xde,0x49,0x6f,0xdc,0xf0,0xe8,0x70,0x36,0xdb,0x90,0x00,0x07,0x9e,0x77,0x39,0xfe}}, + {{0x3e,0x0c,0x21,0xc4,0x3d,0x64,0x61,0xc1,0x9d,0xa1,0x83,0x10,0x74,0x1d,0x56,0x12,0xaf,0x29,0x5c,0x6c,0x12,0x48,0x0a,0xc7,0xe5,0x12,0xb6,0x42,0x6b,0x54,0xf4,0x42,0x0c,0x43,0x42,0x2e,0x78,0xc2,0xe7,0x26,0x09,0x41,0x4a,0x2f,0xa1,0xb0,0x1f,0xcd,0x63,0x76,0x1e,0xa1,0x6f,0xf6,0xe2,0xc2,0x08,0x89,0x0d,0x28,0xbf,0x1b,0x56,0x5b},{0x3e,0x2e,0xf2,0xcc,0x81,0xca,0xa7,0x5d,0x01,0xd2,0x82,0xfd,0x45,0xee,0xc0,0xf5,0x49,0x3b,0xe2,0xa4,0x2a,0x4d,0x5f,0x40,0x0d,0xbc,0xb9,0x3d,0x6e,0xda,0xe2,0x86,0xe1,0x23,0x8b,0x5f,0x0d,0xa2,0x35,0x15,0x1d,0x22,0x23,0xa5,0x69,0x56,0x34,0x78,0xb3,0xb3,0x55,0xef,0x63,0x8a,0x17,0x63,0xda,0xf0,0x64,0x99,0x8a,0x8a,0xba,0xd6},{0x68,0x79,0x36,0xa7,0x6b,0xe3,0x76,0x1c,0xe3,0x38,0x0b,0xa3,0x91,0xb6,0xb0,0x82,0x37,0xfa,0x52,0x74,0xf1,0xb5,0xd5,0xd9,0x07,0x06,0x9e,0xda,0x87,0x6b,0x0f,0x24,0x4f,0xbe,0xc9,0xff,0x03,0x41,0xaf,0x77,0x68,0xed,0xe7,0x71,0xba,0x2d,0xde,0x27,0xa1,0xbf,0xa8,0xa7,0x30,0x7c,0xcb,0x79,0x72,0x89,0x1a,0xdc,0xc1,0xe4,0xb2,0x9d},{0x94,0xa3,0x11,0xf4,0x44,0x80,0xd0,0xa3,0x47,0x93,0x36,0xe2,0xbd,0x04,0xe4,0x74,0x3d,0x00,0x60,0xad,0xd0,0x2d,0x86,0x66,0xa1,0x72,0x1a,0xb9,0x1c,0x14,0xa2,0x9b,0x4b,0x04,0x7d,0x5b,0xcd,0xf8,0x01,0x33,0xde,0x34,0x10,0x29,0xc4,0x72,0x56,0xff,0x11,0xcd,0xd8,0x61,0x2c,0xb6,0xb7,0xf4,0x24,0x8b,0x44,0xb4,0xe7,0x34,0x50,0xb8},{0x72,0xf6,0xd4,0xa3,0x24,0xf9,0xef,0xf4,0x55,0x8d,0x3c,0x07,0xca,0x10,0xdd,0x54,0x87,0x13,0x32,0x78,0x5c,0x64,0x10,0x08,0x62,0x7e,0xf4,0x34,0x0f,0x1c,0xcd,0xcc,0x3b,0x42,0xfe,0x60,0x41,0x70,0x2c,0x6b,0xd4,0x6c,0xf7,0xb8,0x24,0xf6,0xd7,0x07,0xb3,0x46,0xb0,0x7d,0x14,0x24,0x9b,0x72,0x79,0xf4,0x23,0x2a,0xec,0x02,0xe7,0x69},{0xe5,0xbe,0x84,0xc3,0x92,0x47,0x15,0xd3,0xac,0x06,0x44,0x72,0x41,0xeb,0xb6,0x5a,0x17,0x06,0x90,0xd9,0x55,0x3d,0xe4,0x87,0x7d,0x5a,0x11,0x9f,0x02,0x6d,0xd3,0x4e,0x71,0xd1,0x5e,0x16,0x9f,0xb2,0xc0,0x7f,0xcb,0x78,0x8b,0x89,0x11,0xae,0x43,0xe8,0x85,0xb7,0xf9,0xc8,0x48,0x5a,0xb2,0x96,0xaf,0x8f,0xab,0x71,0x84,0x9d,0x40,0x09,0x30,0xd4,0x32,0x6e,0xa2,0x77,0x97,0x71,0x37,0xce,0x22,0x6b,0xca,0xc9,0x79,0xef,0xc0,0xb2,0xb4,0x3d,0x30,0xbf,0x77,0xe9,0xc3,0x8d,0xec,0x15,0x04,0x08,0xfa,0x15},{0x4b,0xf3,0x7f,0xb2,0x78,0x75,0x45,0xd4,0xce,0x5e,0x3d,0xaf,0x92,0x63,0x3d,0x90,0xc0,0xa7,0x23,0x62,0x7f,0x37,0x58,0x8d,0x12,0xe0,0xb8,0x6c,0x46,0x38,0xaa,0xf7,0xe1,0x03,0x9e,0x1f,0x31,0xf9,0x5a,0xa4,0x59,0x0d,0xec,0xc5,0x1f,0x17,0x88,0x25,0xcc,0xed,0x69,0x2b,0x91,0x73,0x6a,0x3f,0xcb,0xe5,0x9c,0x1e,0x26,0x3e,0xec,0x0b,0x30,0xd4,0x32,0x6e,0xa2,0x77,0x97,0x71,0x37,0xce,0x22,0x6b,0xca,0xc9,0x79,0xef,0xc0,0xb2,0xb4,0x3d,0x30,0xbf,0x77,0xe9,0xc3,0x8d,0xec,0x15,0x04,0x08,0xfa,0x15}}, + {{0xc5,0x1d,0xcd,0x70,0xb2,0x9e,0x53,0x29,0x05,0x78,0x83,0x5d,0x56,0x30,0x89,0xee,0x02,0xd7,0xac,0x57,0x0a,0xd2,0xa0,0x9c,0x96,0x0c,0xbf,0xf2,0x30,0xbf,0x1a,0x2b,0xee,0x0e,0x9f,0x1e,0x1c,0x65,0x7d,0xb5,0x48,0xad,0x6f,0x51,0xa0,0x91,0x61,0xe4,0xe6,0x83,0x9f,0x58,0x7c,0x76,0x2b,0x52,0x94,0x87,0x3c,0x8d,0x36,0x4c,0x37,0x3c},{0x59,0x3b,0x0d,0x38,0xab,0x93,0xca,0xfb,0x67,0x44,0x30,0x96,0xec,0xbd,0x00,0x1d,0x93,0xd0,0xb3,0x3d,0x3c,0xd4,0x4e,0x3d,0xd8,0x29,0x93,0xb2,0xb3,0x77,0xfc,0x57,0x31,0x20,0xe3,0x90,0x0d,0xf4,0x91,0x2f,0x8b,0x43,0xce,0xfe,0x99,0x03,0x03,0xa2,0x90,0x8d,0xcf,0xa8,0xc0,0x21,0x00,0xca,0xcc,0xcb,0x4b,0x2f,0xa5,0x39,0xa8,0x0b},{0xca,0xf6,0xf9,0xbb,0x53,0xcb,0x97,0x76,0xb6,0x9c,0x2c,0x18,0x21,0x43,0x13,0x48,0x13,0xc9,0x0e,0xeb,0x40,0xea,0xce,0x1f,0x3a,0xe9,0xd2,0x9e,0x29,0xdb,0xe2,0x79,0xe2,0x1a,0x9f,0x84,0x9d,0xe4,0x55,0x82,0x17,0xeb,0x87,0xf6,0xc3,0xef,0xcd,0x54,0x14,0xee,0xc8,0x5b,0xd7,0x67,0x05,0xe2,0x34,0xa2,0x7e,0x81,0x83,0x21,0x7a,0x02},{0xc5,0x03,0xd9,0x75,0xdf,0x17,0x15,0xe3,0x5b,0x7b,0x4f,0x66,0x9c,0x15,0x4e,0x01,0xdf,0x3d,0x16,0xb6,0x52,0xcc,0xcf,0x28,0x40,0xdb,0x20,0xee,0x8b,0x69,0xb1,0x2b,0xc0,0x6e,0xe4,0xd2,0xf5,0xd1,0x49,0x3f,0xf3,0x0a,0x12,0xcd,0x13,0xbd,0x9d,0x3d,0x5b,0x28,0x5c,0xb0,0x0d,0x0e,0xb6,0xed,0xec,0x65,0xeb,0x25,0x28,0x2e,0x65,0x2f},{0xed,0xa7,0x05,0xc1,0xa6,0x81,0xf2,0x7a,0x69,0x68,0x17,0x8e,0xf7,0xc9,0x14,0x80,0x9f,0x81,0xfe,0x16,0xfd,0x81,0x93,0xb4,0x0b,0x05,0x5b,0x4e,0xef,0x6e,0x7a,0x67,0x9d,0x99,0x4c,0x17,0xcd,0x1c,0x16,0xfd,0x31,0x35,0xd5,0x3e,0xa3,0x00,0xbf,0xbe,0xda,0xd6,0xe2,0x37,0x9b,0x13,0x1b,0xca,0x29,0x90,0x4b,0xf2,0x09,0x57,0x2f,0xe9},{0xd7,0xba,0x23,0xd3,0xa0,0x6e,0x14,0x6a,0xf0,0x77,0xb7,0xe6,0xe3,0xc9,0x3b,0x38,0xbb,0xe7,0xbe,0x54,0x75,0xf8,0xb7,0x42,0x29,0xe2,0x83,0xde,0x20,0x22,0x41,0xcf,0x5f,0x6f,0x80,0x60,0xf3,0x44,0x04,0x21,0xd5,0x03,0x68,0x42,0xde,0x81,0xea,0xe8,0x7e,0x5b,0x80,0x0f,0x1b,0x2d,0x06,0xc7,0xce,0xe9,0x46,0xc7,0xf7,0xb3,0xa2,0x02,0x21,0xb5,0x4d,0xc2,0x36,0xea,0xe6,0x7b,0xb3,0x61,0xe6,0x18,0x40,0x5b,0xce,0x5b,0xc2,0xee,0xa5,0xde,0xe9,0xe6,0xe0,0xa8,0x58,0x58,0x03,0x34,0x26,0x27,0x65,0x2a},{0xfa,0x43,0xa6,0xc4,0x32,0xa1,0x2f,0xb6,0x37,0x05,0xf4,0xa4,0xa7,0x36,0xdd,0x1c,0x45,0x10,0x95,0x83,0x67,0x89,0x79,0x18,0x34,0xad,0xe7,0x57,0x7f,0x0d,0x48,0x9b,0x14,0xdf,0x5f,0xc8,0xd7,0x0f,0x78,0x47,0x88,0x20,0xff,0x7f,0xb1,0x21,0x27,0x14,0x58,0x32,0x12,0xfb,0x97,0xe0,0x81,0x0e,0x92,0xf4,0x5c,0x0e,0x44,0x48,0x4e,0x01,0x21,0xb5,0x4d,0xc2,0x36,0xea,0xe6,0x7b,0xb3,0x61,0xe6,0x18,0x40,0x5b,0xce,0x5b,0xc2,0xee,0xa5,0xde,0xe9,0xe6,0xe0,0xa8,0x58,0x58,0x03,0x34,0x26,0x27,0x65,0x2a}}, + {{0x1e,0x89,0x12,0xe8,0xab,0xca,0xeb,0x96,0x78,0x43,0x89,0x79,0x26,0x61,0x86,0x2e,0x37,0xd7,0x94,0xb5,0xb9,0xf7,0xc9,0xe7,0x04,0x6c,0x96,0x1c,0x54,0x0d,0xb0,0x6c,0xd3,0x68,0x9b,0x53,0xa7,0x56,0x34,0x1b,0x65,0xff,0xf9,0xee,0xf1,0xc6,0xfd,0x7e,0xa8,0x42,0x59,0x60,0x06,0x5f,0xc2,0x89,0x8b,0xfc,0xf8,0x6c,0x9a,0x0d,0xb1,0x36},{0x52,0x3d,0x83,0x25,0x0f,0x57,0x81,0x76,0x7b,0x21,0xf7,0x96,0xd6,0x1f,0xfe,0xd7,0x7c,0xc1,0x32,0xb5,0xbc,0x05,0x46,0xdb,0x6f,0x25,0xd8,0x7a,0x68,0xe2,0x01,0x81,0xf8,0x9a,0xc5,0x29,0x78,0x1c,0x01,0xc5,0x4d,0x61,0x4e,0x75,0xdf,0x9f,0xc3,0x22,0x96,0x7c,0xf9,0xa7,0xed,0x41,0x6f,0x64,0xfd,0xd4,0x61,0x58,0x0d,0x49,0xc9,0xa4},{0x4a,0xf7,0xda,0xef,0xe0,0x3b,0x33,0x19,0x79,0x02,0x7a,0xbb,0xd3,0x53,0xf4,0x8c,0x8a,0x16,0xfb,0xbd,0x35,0xd9,0x70,0xb2,0x0a,0x06,0x05,0x14,0xd0,0x9e,0xf6,0x13,0x44,0xbb,0xb7,0x93,0x86,0x1b,0x3c,0xb0,0x54,0xa7,0x48,0xc2,0xa7,0x10,0xda,0x65,0xb2,0xdb,0x0f,0x85,0x23,0x57,0x77,0x44,0x23,0x20,0x6d,0x2e,0xde,0x20,0x01,0xed},{0x9c,0xb8,0x68,0xeb,0xbb,0x8b,0xaf,0x81,0x9c,0x2f,0x90,0x4c,0xc2,0x62,0x17,0xfc,0xf2,0xa5,0xab,0x4c,0x2e,0x69,0xcb,0x82,0x5f,0x4c,0x3c,0x82,0xcd,0x6a,0xcb,0x15,0xa2,0xfc,0x50,0x54,0x5e,0x2e,0x83,0x52,0x48,0x29,0x51,0xcc,0x50,0xaa,0x27,0xa3,0xf3,0x71,0xdb,0x2c,0x1c,0xa9,0x8a,0xa5,0x95,0xab,0x3e,0x6f,0xcd,0xba,0x22,0x7c},{0xf7,0x5d,0xb5,0x20,0x65,0xfe,0xa9,0xe7,0x1f,0x8e,0xd6,0xc0,0xf2,0x3f,0x1b,0x8c,0x7a,0x02,0x54,0xd8,0xa7,0x0e,0x6f,0x68,0x94,0x81,0xff,0x30,0x0e,0x6d,0x1a,0x96,0x1b,0x86,0x07,0xaa,0xbf,0x37,0xc5,0x5e,0x26,0xa2,0xdf,0x0b,0xd0,0x7f,0x94,0x35,0x30,0xa4,0x9e,0x47,0xaf,0xad,0x9c,0xc9,0x02,0x21,0x55,0x94,0x04,0x13,0xff,0x64},{0x9c,0x8d,0x18,0x63,0x83,0xad,0x01,0xcc,0xbb,0xe6,0x00,0xda,0x15,0xce,0xc6,0x6e,0x7a,0x37,0x6a,0x81,0x44,0xb3,0xfc,0xb7,0xcd,0x05,0xee,0x4a,0x6f,0x29,0xe4,0x79,0x63,0x52,0x7e,0x14,0xc9,0x14,0x77,0xa8,0x19,0x94,0x03,0xc6,0x51,0x57,0xf1,0xcc,0x11,0x29,0xde,0x86,0x08,0xfe,0x41,0x02,0x71,0xb7,0xbf,0xd7,0xe7,0x83,0x3e,0x0c,0x9a,0x59,0x7e,0xe8,0x61,0x36,0x56,0x9a,0xbf,0x64,0xfd,0xf3,0xb7,0xb9,0x2f,0x9e,0x56,0x1f,0x57,0x45,0x2e,0x19,0x0f,0x6f,0x70,0x01,0xc2,0x48,0x05,0x23,0x9b,0x2f},{0xb5,0x4e,0xe7,0xcc,0x7b,0x66,0x7a,0xf8,0xec,0xcd,0x1b,0x0c,0x0f,0xec,0x04,0x27,0xa0,0x61,0xfd,0x12,0x2d,0xab,0xc9,0xc5,0x8e,0xee,0x36,0xc2,0xef,0x67,0xd5,0x87,0x95,0x6c,0x12,0xb7,0x12,0x81,0x55,0xe0,0x7b,0xdb,0x8f,0x67,0xea,0x04,0x55,0x91,0x9b,0x50,0x65,0x05,0xc1,0xf1,0x0b,0x04,0x91,0x66,0x3c,0x32,0x53,0x72,0x01,0x04,0x9a,0x59,0x7e,0xe8,0x61,0x36,0x56,0x9a,0xbf,0x64,0xfd,0xf3,0xb7,0xb9,0x2f,0x9e,0x56,0x1f,0x57,0x45,0x2e,0x19,0x0f,0x6f,0x70,0x01,0xc2,0x48,0x05,0x23,0x9b,0x2f}}, + {{0xc8,0x37,0x10,0xdc,0xdb,0xfc,0x51,0x91,0xae,0x37,0xa4,0xe0,0xcf,0xbb,0xdd,0x92,0x93,0x5f,0x6b,0xd6,0x81,0xbf,0x9b,0x24,0x5e,0x0d,0xf1,0xe4,0x04,0x89,0xd1,0x1b,0xb2,0x68,0x56,0x3a,0xdc,0x59,0xd0,0x8a,0x93,0x37,0x5d,0xa5,0x40,0x5e,0xfe,0xc9,0x41,0x0b,0x8a,0x50,0xd2,0xa0,0x94,0x86,0xf7,0x46,0x3b,0x7e,0x1d,0xea,0x2b,0xa8},{0x1b,0xe2,0xe6,0x48,0x86,0xa8,0x65,0xfd,0x2b,0xae,0xc7,0x7d,0x41,0xee,0xb2,0x80,0x33,0x1c,0x0a,0xdc,0x42,0xea,0x99,0xd0,0x1f,0x6d,0xc8,0x80,0x51,0x70,0xd4,0x19,0xae,0xfc,0x66,0x16,0xa2,0x53,0x27,0x19,0x7a,0xf2,0x9a,0x25,0x0c,0x39,0x8c,0xbf,0xe7,0xa3,0x7a,0xd6,0xa3,0x43,0x62,0xd2,0x4a,0xc2,0xf1,0x96,0x7e,0xe3,0x83,0x13},{0xf5,0xb1,0x2a,0xc5,0x4d,0xcc,0xdf,0x56,0xde,0x92,0x96,0x46,0x03,0x11,0xfc,0xa0,0xbc,0xa2,0x22,0xf7,0x25,0x74,0x2a,0x1f,0x27,0x34,0x18,0xe8,0x06,0xa4,0x77,0x26,0x1a,0x51,0x5e,0xfb,0x77,0xbc,0x55,0xb1,0xf8,0xa5,0x19,0x23,0x00,0x97,0xf7,0xbb,0xe4,0xcd,0x41,0x9e,0xd9,0x5e,0x0c,0x6b,0x1b,0x8a,0xba,0x52,0x93,0xbe,0x2c,0xf3},{0xb3,0x02,0xeb,0x44,0x3c,0x05,0xae,0x9c,0x94,0xa9,0x1f,0x72,0x41,0xbc,0x81,0x66,0x5f,0x50,0xc0,0x57,0xb4,0x44,0xf0,0xe1,0x2a,0xa9,0x88,0x69,0xa6,0x1c,0x05,0x85,0xda,0xc7,0xb2,0xe1,0x8c,0x2f,0x7c,0x49,0x37,0xa2,0xf2,0x56,0xab,0x12,0x9f,0x12,0x4b,0x1b,0x73,0x75,0x3f,0x30,0x0f,0x40,0xf1,0xf9,0x1d,0xa7,0x2c,0x98,0x8c,0x91},{0xcb,0xd3,0x39,0x60,0x56,0xe3,0xbd,0x65,0x86,0x1a,0x58,0x40,0xc0,0xa4,0xc4,0x8b,0xe5,0xf7,0x49,0x0a,0xf2,0x09,0x51,0x32,0x6e,0x06,0x5a,0x27,0x19,0x78,0x2e,0x3a,0x04,0xf9,0x34,0x80,0x49,0x39,0x93,0xcd,0x89,0x67,0x7b,0xc0,0x8d,0x9d,0x8d,0x4c,0x83,0x20,0x80,0xfc,0x00,0xf2,0x8a,0x8f,0xa4,0x4d,0x8e,0x8f,0x58,0x51,0x5b,0x71},{0x71,0x3f,0x90,0x41,0xb8,0x74,0xbc,0x7a,0x85,0xf5,0xab,0xca,0x7e,0xf2,0x70,0x41,0xbc,0x36,0xb5,0xc3,0x4e,0xf1,0x2b,0x17,0x35,0x40,0xdb,0x3c,0xdb,0xd2,0xec,0x0b,0x99,0xc1,0x43,0x17,0xad,0x38,0x45,0x2d,0x07,0x31,0xd7,0xb6,0x95,0x1c,0x89,0x25,0xe4,0x89,0x97,0xd3,0xcf,0x11,0x2f,0x63,0x31,0x51,0xa2,0x18,0xfc,0x12,0x04,0x0a,0xb0,0x33,0xce,0x0b,0x57,0xc0,0x8c,0x58,0x25,0xf8,0x9b,0x50,0x22,0x1c,0x5c,0x7b,0x02,0xc7,0xed,0xfc,0x98,0x8b,0xbd,0xd2,0x4e,0xfc,0x78,0x91,0x7f,0x4c,0x99,0x24},{0xfc,0x46,0xe4,0x85,0x0c,0x52,0x14,0xf8,0x8a,0xa4,0x97,0x17,0x10,0xb2,0x93,0xef,0xa0,0x66,0x3c,0xfd,0x61,0x42,0x24,0x30,0x70,0x4b,0xfd,0x0b,0x86,0xc8,0x97,0xd7,0x04,0xc2,0xa6,0x61,0x41,0xaf,0xcc,0x1d,0x52,0xc9,0xf3,0xca,0xe1,0x90,0x7c,0xbd,0xce,0xaf,0x30,0xc4,0xb4,0x7d,0x81,0x7e,0xbd,0xe2,0x09,0x70,0x1e,0x6b,0xb9,0x03,0xb0,0x33,0xce,0x0b,0x57,0xc0,0x8c,0x58,0x25,0xf8,0x9b,0x50,0x22,0x1c,0x5c,0x7b,0x02,0xc7,0xed,0xfc,0x98,0x8b,0xbd,0xd2,0x4e,0xfc,0x78,0x91,0x7f,0x4c,0x99,0x24}}, + {{0x5f,0x01,0x6d,0xec,0x82,0x02,0x96,0x47,0x74,0xd9,0x73,0x2e,0x2e,0x17,0x00,0xb6,0xe0,0xa4,0x13,0x17,0xae,0x7f,0x85,0xcb,0xff,0xe7,0x96,0x99,0xdb,0x9f,0xad,0x21,0x60,0xd9,0x12,0xdc,0x41,0x01,0x33,0x66,0x4c,0x24,0x8b,0x25,0x17,0xd7,0x22,0x14,0x12,0x4d,0xad,0x82,0x9a,0x85,0x69,0x5e,0x35,0x10,0xe0,0xd7,0x1a,0x82,0x88,0x14},{0xab,0x5f,0x2c,0x7d,0xa2,0xe5,0x67,0x5f,0xe4,0x92,0x03,0x93,0xd7,0x13,0xa1,0xfa,0x4a,0xb7,0x18,0x4a,0x8e,0x8c,0x78,0x9a,0x0c,0x60,0x02,0xe8,0x2d,0x50,0x05,0x0f,0x92,0xee,0x9f,0x81,0xde,0x6b,0x20,0xe4,0x9b,0x17,0x2e,0x99,0x0f,0x01,0x31,0xa7,0xc5,0xc4,0x53,0x70,0xda,0x03,0xc6,0xf7,0x22,0x87,0x98,0x87,0x19,0x36,0xa6,0x49},{0x93,0xab,0x22,0xc4,0x39,0x6c,0x97,0x80,0xd2,0xe2,0x36,0xfa,0x31,0x74,0x67,0xcc,0x50,0x1b,0x95,0xbe,0x77,0xe0,0xd1,0x00,0x74,0x04,0xe1,0x4d,0xca,0x44,0x35,0x72,0x74,0x69,0x82,0x23,0x56,0x9b,0xcc,0x34,0x5a,0xcb,0xa2,0xa3,0x31,0x12,0x4a,0x84,0x4c,0xe9,0x37,0x3a,0x58,0xf8,0x79,0x65,0x4a,0x66,0x79,0x82,0xf4,0x5d,0x75,0xc3},{0x2d,0x5d,0xac,0x4f,0xb5,0x00,0x68,0x3b,0x5f,0x2e,0xdd,0xcb,0x14,0x4a,0x7f,0xad,0x12,0x45,0x91,0xd1,0x84,0xd8,0x14,0xff,0xcb,0x64,0x43,0x6d,0x65,0xe7,0x19,0x68,0x2b,0x5e,0x53,0x05,0x74,0x66,0xed,0xac,0x2f,0x5a,0x8f,0x70,0x96,0xab,0x29,0xf3,0x9a,0x59,0xa2,0xe2,0xef,0xd3,0xc9,0xd7,0x53,0xf8,0xf5,0xa3,0xd6,0xf4,0x34,0xf8},{0x1d,0x14,0xf3,0xfd,0xb0,0x66,0x20,0xff,0xfc,0x79,0x47,0xc7,0x4c,0xe9,0x45,0x67,0xf5,0x97,0x14,0xea,0x7c,0x63,0xc5,0x3f,0x0b,0x46,0xe0,0x88,0xd6,0x9b,0x67,0x71,0xba,0xa6,0x15,0x28,0x94,0x54,0x83,0x68,0x00,0x3a,0x33,0xa6,0x1a,0x05,0x6a,0x68,0x72,0x98,0x48,0x71,0xea,0x5b,0x47,0xf5,0x80,0x46,0xa9,0x57,0x84,0xec,0xad,0xfc},{0xa3,0x1d,0x87,0xd3,0x28,0x62,0xc6,0xf7,0xdb,0xfb,0xfa,0xfc,0xf3,0x27,0x5c,0x31,0xd3,0x32,0x26,0x0e,0x0f,0x41,0x49,0xec,0x05,0x16,0xf7,0xa5,0x63,0xb3,0xbc,0xe5,0x0d,0x1e,0x6f,0x97,0x4f,0x68,0x40,0xc0,0xd4,0x6c,0x4f,0x9e,0x25,0xd0,0xab,0x8d,0x2a,0xb9,0x3e,0x06,0x4d,0x9d,0x3d,0x2d,0x79,0x8d,0x93,0xdc,0xfc,0x6f,0x0b,0x04,0x48,0x7c,0x19,0x5c,0xa9,0xc8,0x44,0xe5,0xf6,0x4f,0x51,0xd8,0x72,0x63,0x41,0xda,0x62,0xac,0x78,0x73,0xb3,0x3e,0xc8,0xb2,0xf1,0x3f,0x89,0xf2,0x0e,0x95,0xdf,0xed},{0xfd,0x69,0xb1,0x9a,0xdb,0xae,0x95,0x87,0xe2,0xc6,0x8a,0x97,0x0c,0xee,0xc4,0x22,0x60,0x4e,0x96,0xa9,0x72,0xb9,0x6f,0x86,0x97,0xa8,0xdf,0x83,0xc5,0x18,0x18,0x6e,0xc9,0x43,0x30,0x7e,0x5b,0xcf,0x37,0x0f,0xc1,0xd7,0xe5,0xab,0xb1,0x31,0xe0,0x97,0xc7,0x53,0xb7,0xfd,0xd7,0xdf,0x00,0x43,0x0e,0x41,0x62,0x80,0x0b,0xe3,0xe0,0x06,0x48,0x7c,0x19,0x5c,0xa9,0xc8,0x44,0xe5,0xf6,0x4f,0x51,0xd8,0x72,0x63,0x41,0xda,0x62,0xac,0x78,0x73,0xb3,0x3e,0xc8,0xb2,0xf1,0x3f,0x89,0xf2,0x0e,0x95,0xdf,0xed}}, + {{0x98,0x29,0xf7,0x57,0xfd,0xbd,0x44,0x3f,0xd9,0x90,0x98,0x19,0x97,0xf2,0x60,0x27,0xfd,0x08,0xfc,0x8a,0xc6,0xaf,0x87,0x22,0x7f,0x74,0x4a,0x80,0xaf,0x72,0x00,0x01,0x70,0x9b,0x47,0x2a,0xd2,0x8e,0x41,0x0a,0xea,0x6a,0xdf,0xb7,0x61,0x54,0x89,0x5e,0x01,0x9f,0x76,0x64,0x29,0xee,0x8d,0x85,0x20,0xff,0x30,0x58,0xc2,0xa3,0x2a,0x56},{0xea,0x69,0x8e,0x6b,0x8e,0xdd,0x55,0x22,0x45,0x61,0xd4,0x92,0x66,0x8e,0x96,0xaf,0x7e,0x40,0x28,0x72,0xc4,0x46,0xe7,0x88,0xd4,0x6c,0x74,0xb7,0x48,0x7f,0xe8,0xe1,0x5e,0xa5,0x85,0x62,0x8f,0xd6,0xfc,0x27,0x0a,0xb2,0x4b,0x38,0x94,0x59,0x52,0x0d,0x6a,0x4d,0xe5,0x61,0xce,0x0d,0x44,0x03,0xa6,0x2a,0xc2,0xd4,0xd4,0xe2,0x71,0xe3},{0x40,0xf0,0x82,0xf0,0x8d,0xaa,0xad,0xa9,0x9f,0x9b,0x85,0x02,0xcf,0x57,0x15,0x41,0x13,0x59,0xf2,0xba,0xdd,0xbf,0x93,0xe5,0x40,0x2e,0xaf,0xdd,0x43,0x52,0xc8,0x7f,0x40,0xad,0x91,0x5b,0x58,0xd1,0xa1,0xe8,0x6f,0x77,0xc3,0x41,0x35,0x5e,0xf7,0x03,0xba,0xe4,0xed,0x2c,0x28,0x59,0xd6,0x48,0xfe,0x50,0xcc,0xf9,0x80,0xd1,0x49,0xd1},{0xd7,0xa5,0xd9,0x13,0xdf,0x7d,0xf6,0xc6,0x25,0x0f,0x52,0xc2,0x57,0x61,0x20,0xf2,0xf0,0xdb,0x47,0x49,0x56,0xaf,0x89,0x11,0xa7,0x8d,0x09,0x3a,0xfe,0x45,0x43,0xef,0x9f,0x0c,0x42,0xaf,0xa8,0xcc,0x60,0x48,0xc0,0x1c,0x7c,0xbe,0x01,0xe2,0x88,0xcc,0x6c,0x3e,0x97,0x91,0xf3,0xd9,0xb2,0xb2,0x09,0x7e,0x35,0xb1,0x78,0xb4,0x03,0xf6},{0x08,0xc4,0x1a,0x3a,0xc3,0xe3,0x26,0xbd,0x8d,0xee,0x5d,0xf0,0xba,0xb6,0x65,0xff,0x77,0xc0,0x99,0xd1,0xca,0xdc,0xf5,0x4b,0x50,0x50,0x0a,0x9e,0x13,0x33,0x76,0x86,0x9b,0x39,0x79,0x78,0x73,0x5c,0x2f,0x69,0xa9,0x9e,0x0b,0xeb,0x11,0x1e,0x12,0xaa,0xc1,0x09,0x83,0x0f,0xca,0xcb,0x95,0x10,0xde,0x85,0xe3,0x75,0x62,0x4a,0xc2,0x4c},{0x68,0x78,0x6c,0xce,0x2f,0x72,0x80,0xfe,0x83,0x88,0x63,0x37,0xa7,0xa1,0x5a,0x0b,0x84,0x8a,0xda,0x28,0x84,0xf1,0x6a,0x63,0x24,0x1c,0x72,0xda,0x84,0xee,0x1d,0xe0,0x77,0xf0,0xf6,0xce,0x7e,0x79,0x0a,0x55,0x03,0x01,0x13,0x0f,0xf7,0x6b,0x45,0xe7,0xcb,0xfd,0xb0,0x37,0x93,0x4b,0x40,0x69,0xe0,0x77,0x67,0x72,0x65,0xee,0x35,0x08,0x00,0xc0,0x07,0x10,0xd8,0x6e,0x55,0x83,0x5a,0xbc,0xfa,0x67,0x80,0x8f,0xfa,0x21,0x3e,0x56,0x53,0x5b,0xbc,0x9d,0xff,0x16,0xd9,0x57,0xcf,0x2b,0x78,0x06,0x5a,0x89},{0xdf,0x32,0x1a,0x01,0x84,0xe5,0xb8,0x2c,0x70,0x6c,0xeb,0xd1,0xf0,0xb4,0x9b,0x32,0xc8,0xd0,0x81,0xc4,0xea,0xb2,0x7c,0x32,0x1a,0x02,0x61,0xf2,0xd9,0x4d,0xe5,0x85,0xad,0xfc,0xc6,0x70,0xee,0x85,0x77,0x07,0x9b,0x5d,0x5f,0x88,0xef,0xb6,0xd8,0xdf,0x2b,0xa2,0x4d,0x90,0x11,0x2d,0x38,0x3f,0xa8,0x84,0xf0,0x76,0xdd,0x31,0xd0,0x09,0x00,0xc0,0x07,0x10,0xd8,0x6e,0x55,0x83,0x5a,0xbc,0xfa,0x67,0x80,0x8f,0xfa,0x21,0x3e,0x56,0x53,0x5b,0xbc,0x9d,0xff,0x16,0xd9,0x57,0xcf,0x2b,0x78,0x06,0x5a,0x89}}, + {{0x25,0x87,0x1e,0x6f,0xe8,0xd0,0xde,0x1d,0xd5,0xf2,0xd3,0x5b,0xff,0x9e,0x67,0x99,0x60,0xb4,0x0e,0xb7,0x98,0x1b,0x2a,0x3a,0x9c,0xec,0xc1,0xe1,0x2e,0x2b,0xc0,0x3e,0x3c,0xfb,0x64,0x91,0x72,0xc6,0x7e,0x57,0x47,0x00,0x97,0xbf,0x8e,0x0e,0xbf,0xad,0xd9,0x28,0x86,0x7c,0xfd,0x41,0x91,0xae,0x2d,0xee,0xc0,0xb2,0x32,0x7d,0x99,0x7d},{0x63,0xc1,0xf9,0x61,0x9c,0x9e,0x1a,0xd7,0xca,0xa3,0x71,0xd6,0x34,0x3d,0xa7,0x08,0x36,0x0c,0xec,0x37,0x35,0x94,0x1a,0x45,0xa9,0xfa,0xf2,0xb5,0x25,0x92,0xbf,0xd1,0x1e,0xca,0xdd,0x5a,0x23,0xad,0x9e,0x45,0xc3,0x66,0xcb,0x8f,0xda,0xa3,0xd1,0xe6,0x27,0x38,0x11,0x54,0x67,0x31,0x03,0x64,0x35,0xe0,0x68,0x0b,0x93,0xee,0x81,0x17},{0x8b,0x01,0xe9,0x99,0x54,0x54,0x73,0x15,0x0b,0xac,0x38,0x7b,0xe9,0xe3,0x17,0x4f,0x02,0x3e,0xe3,0x8e,0xda,0x41,0xa0,0x9d,0x10,0xe0,0xda,0x11,0xfe,0xec,0x2f,0x42,0xe7,0xc8,0xb3,0xde,0x2f,0x7b,0xfd,0xdf,0x7c,0x34,0x3b,0x5e,0xac,0x22,0x8c,0x99,0x3d,0xa1,0xa9,0xd9,0x81,0xb6,0x51,0xc8,0xaf,0x3e,0x75,0xed,0x45,0xcf,0xf7,0xb9},{0xaf,0xe9,0x9c,0x16,0x4a,0x8f,0x3b,0x0f,0xef,0x71,0x2f,0xaa,0x8d,0x7d,0xce,0xed,0xea,0x31,0x93,0xaf,0x2c,0x75,0xc6,0xfa,0xda,0x3e,0xa6,0xea,0x2a,0x3e,0x7b,0x72,0xb6,0xf8,0xd7,0x9a,0x88,0xcb,0x0b,0x81,0x97,0x24,0x29,0x3b,0x11,0x23,0x69,0xc2,0xff,0x98,0x39,0x25,0x99,0xae,0xe1,0x07,0x3e,0x97,0xde,0x10,0x21,0x23,0x7a,0x2d},{0xbe,0x2f,0xb9,0x4c,0x41,0x5a,0x9a,0xf6,0xfb,0xf8,0x26,0x9d,0x81,0x7f,0x39,0x91,0xaf,0x5b,0xf1,0xd7,0x93,0x0a,0xdf,0x18,0x19,0x4a,0x80,0x74,0x14,0x98,0x2b,0xf2,0x3b,0x25,0xc5,0xe8,0xfc,0x07,0x3f,0x5d,0xa1,0x39,0x27,0x4e,0x1c,0xd2,0x7a,0xfe,0x3e,0x7b,0x03,0x35,0x15,0x9e,0x35,0x2b,0xd0,0xbe,0x67,0x48,0x42,0xdd,0xa4,0xdd},{0xbd,0xcd,0xd7,0xbf,0xb1,0x0a,0xdb,0x9f,0x85,0x42,0xba,0xf4,0xc8,0xff,0xb0,0xe1,0x9a,0x18,0x6d,0x1a,0xe0,0x37,0xc1,0xa2,0xe1,0x1c,0x38,0x55,0x14,0xbf,0x64,0x67,0x84,0x47,0xb6,0x0a,0xf6,0x93,0xf1,0x10,0xab,0x09,0xf0,0x60,0x84,0xe2,0x4e,0x4b,0x5e,0xa2,0xd2,0xd1,0x19,0x22,0xd7,0xc4,0x85,0x13,0x23,0xa3,0x6a,0xb6,0x75,0x0f,0x43,0xe6,0xde,0x7b,0x67,0x2a,0x73,0x77,0x9e,0xb4,0x94,0x6c,0xc3,0x9a,0x67,0x51,0xcf,0xe9,0x47,0x46,0x0e,0x3a,0x12,0x7d,0x7c,0x66,0x73,0x6c,0xd5,0x4a,0x21,0x4d},{0x89,0x7e,0xd0,0xbf,0x2e,0x9f,0x0c,0xff,0x6e,0x56,0x25,0x9b,0x79,0x99,0x52,0x27,0xc2,0x3a,0xaa,0xf0,0x47,0x6d,0xed,0x05,0xa1,0xeb,0x9c,0x92,0x28,0x7f,0x1b,0xc8,0x1c,0x57,0x76,0xab,0x05,0xe3,0xd3,0xb7,0xa3,0xf5,0xac,0xa8,0x21,0x33,0x7c,0xb7,0xe7,0xc2,0xd0,0x25,0x6f,0xdf,0x34,0xd1,0xb0,0x34,0x41,0x46,0x30,0x9c,0x76,0x07,0x43,0xe6,0xde,0x7b,0x67,0x2a,0x73,0x77,0x9e,0xb4,0x94,0x6c,0xc3,0x9a,0x67,0x51,0xcf,0xe9,0x47,0x46,0x0e,0x3a,0x12,0x7d,0x7c,0x66,0x73,0x6c,0xd5,0x4a,0x21,0x4d}} +}; + +////////////////////////////////////////////////////////////////////////////// + +static int testCrypto() +{ + unsigned char buf1[16384]; + unsigned char buf2[sizeof(buf1)],buf3[sizeof(buf1)]; + + for(int i=0;i<3;++i) { + Utils::getSecureRandom(buf1,64); + std::cout << "[crypto] getSecureRandom: " << Utils::hex(buf1,64) << std::endl; + } + + std::cout << "[crypto] Testing Salsa20... "; std::cout.flush(); + for(unsigned int i=0;i<4;++i) { + for(unsigned int k=0;kp2 should equal p1<>p2 + if (memcmp(buf1,buf2,64)) { + std::cout << "FAIL (1)" << std::endl; + return -1; + } + // p2<>p1 should not equal p3<>p1 + if (!memcmp(buf2,buf3,64)) { + std::cout << "FAIL (2)" << std::endl; + return -1; + } + } + std::cout << "PASS" << std::endl; + + std::cout << "[crypto] Benchmarking C25519 ECC key agreement... "; std::cout.flush(); + C25519::Pair bp[8]; + for(int k=0;k<8;++k) + bp[k] = C25519::generate(); + const uint64_t st = OSUtils::now(); + for(unsigned int k=0;k<50;++k) { + C25519::agree(bp[~k & 7],bp[k & 7].pub,buf1,64); + } + const uint64_t et = OSUtils::now(); + std::cout << ((double)(et - st) / 50.0) << "ms per agreement." << std::endl; + + std::cout << "[crypto] Testing Ed25519 ECC signatures... "; std::cout.flush(); + C25519::Pair didntSign = C25519::generate(); + for(unsigned int i=0;i<10;++i) { + C25519::Pair p1 = C25519::generate(); + for(unsigned int k=0;k buf; + + std::cout << "[identity] Validate known-good identity... "; std::cout.flush(); + if (!id.fromString(KNOWN_GOOD_IDENTITY)) { + std::cout << "FAIL (1)" << std::endl; + return -1; + } + const uint64_t vst = OSUtils::now(); + for(int k=0;k<10;++k) { + if (!id.locallyValidate()) { + std::cout << "FAIL (2)" << std::endl; + return -1; + } + } + const uint64_t vet = OSUtils::now(); + std::cout << "PASS (" << ((double)(vet - vst) / 10.0) << "ms per validation)" << std::endl; + + std::cout << "[identity] Validate known-bad identity... "; std::cout.flush(); + if (!id.fromString(KNOWN_BAD_IDENTITY)) { + std::cout << "FAIL (1)" << std::endl; + return -1; + } + if (id.locallyValidate()) { + std::cout << "FAIL (2)" << std::endl; + return -1; + } + std::cout << "PASS (i.e. it failed)" << std::endl; + + for(unsigned int k=0;k<4;++k) { + std::cout << "[identity] Generate identity... "; std::cout.flush(); + uint64_t genstart = OSUtils::now(); + id.generate(); + uint64_t genend = OSUtils::now(); + std::cout << "(took " << (genend - genstart) << "ms): " << id.toString(true) << std::endl; + std::cout << "[identity] Locally validate identity: "; + if (id.locallyValidate()) { + std::cout << "PASS" << std::endl; + } else { + std::cout << "FAIL" << std::endl; + return -1; + } + } + + { + Identity id2; + buf.clear(); + id.serialize(buf,true); + id2.deserialize(buf); + std::cout << "[identity] Serialize and deserialize (w/private): "; + if ((id == id2)&&(id2.locallyValidate())) { + std::cout << "PASS" << std::endl; + } else { + std::cout << "FAIL" << std::endl; + return -1; + } + } + + { + Identity id2; + buf.clear(); + id.serialize(buf,false); + id2.deserialize(buf); + std::cout << "[identity] Serialize and deserialize (no private): "; + if ((id == id2)&&(id2.locallyValidate())) { + std::cout << "PASS" << std::endl; + } else { + std::cout << "FAIL" << std::endl; + return -1; + } + } + + { + Identity id2; + id2.fromString(id.toString(true).c_str()); + std::cout << "[identity] Serialize and deserialize (ASCII w/private): "; + if ((id == id2)&&(id2.locallyValidate())) { + std::cout << "PASS" << std::endl; + } else { + std::cout << "FAIL" << std::endl; + return -1; + } + } + + { + Identity id2; + id2.fromString(id.toString(false).c_str()); + std::cout << "[identity] Serialize and deserialize (ASCII no private): "; + if ((id == id2)&&(id2.locallyValidate())) { + std::cout << "PASS" << std::endl; + } else { + std::cout << "FAIL" << std::endl; + return -1; + } + } + + return 0; +} + +static int testCertificate() +{ + Identity authority; + std::cout << "[certificate] Generating identity to act as authority... "; std::cout.flush(); + authority.generate(); + std::cout << authority.address().toString() << std::endl; + + Identity idA,idB; + std::cout << "[certificate] Generating identities A and B... "; std::cout.flush(); + idA.generate(); + idB.generate(); + std::cout << idA.address().toString() << ", " << idB.address().toString() << std::endl; + + std::cout << "[certificate] Generating certificates A and B..."; + CertificateOfMembership cA(10000,100,1,idA.address()); + CertificateOfMembership cB(10099,100,1,idB.address()); + std::cout << std::endl; + + std::cout << "[certificate] Signing certificates A and B with authority..."; + cA.sign(authority); + cB.sign(authority); + std::cout << std::endl; + + //std::cout << "[certificate] A: " << cA.toString() << std::endl; + //std::cout << "[certificate] B: " << cB.toString() << std::endl; + + std::cout << "[certificate] A agrees with B and B with A... "; + if (cA.agreesWith(cB)) + std::cout << "yes, "; + else { + std::cout << "FAIL" << std::endl; + return -1; + } + if (cB.agreesWith(cA)) + std::cout << "yes." << std::endl; + else { + std::cout << "FAIL" << std::endl; + return -1; + } + + std::cout << "[certificate] Generating two certificates that should not agree..."; + cA = CertificateOfMembership(10000,100,1,idA.address()); + cB = CertificateOfMembership(10101,100,1,idB.address()); + std::cout << std::endl; + + std::cout << "[certificate] A agrees with B and B with A... "; + if (!cA.agreesWith(cB)) + std::cout << "no, "; + else { + std::cout << "FAIL" << std::endl; + return -1; + } + if (!cB.agreesWith(cA)) + std::cout << "no." << std::endl; + else { + std::cout << "FAIL" << std::endl; + return -1; + } + + return 0; +} + +static int testPacket() +{ + unsigned char salsaKey[32]; + Packet a,b; + + a.burn(); + b.burn(); + + for(unsigned int i=0;i<32;++i) + salsaKey[i] = (unsigned char)rand(); + + std::cout << "[packet] Testing Packet encoder/decoder... "; + + a.reset(Address(),Address(),Packet::VERB_HELLO); + for(int i=0;i<32;++i) + a.append("supercalifragilisticexpealidocious",(unsigned int)strlen("supercalifragilisticexpealidocious")); + + b = a; + if (a != b) { + std::cout << "FAIL (assign)" << std::endl; + return -1; + } + + a.compress(); + unsigned int complen = a.size(); + a.uncompress(); + + std::cout << "(compressed: " << complen << ", decompressed: " << a.size() << ") "; + if (a != b) { + std::cout << "FAIL (compresssion)" << std::endl; + return -1; + } + + a.armor(salsaKey,true,0); + if (!a.dearmor(salsaKey)) { + std::cout << "FAIL (encrypt-decrypt/verify)" << std::endl; + return -1; + } + + std::cout << "PASS" << std::endl; + return 0; +} + +static void _testExcept(int &depth) +{ + if (depth >= 16) { + throw std::runtime_error("LOL!"); + } else { + ++depth; + _testExcept(depth); + } +} + +static int testOther() +{ + std::cout << "[other] Testing C++ exceptions... "; std::cout.flush(); + int depth = 0; + try { + _testExcept(depth); + } catch (std::runtime_error &e) { + if (depth == 16) { + std::cout << "OK" << std::endl; + } else { + std::cout << "ERROR (depth not 16)" << std::endl; + return -1; + } + } catch ( ... ) { + std::cout << "ERROR (exception not std::runtime_error)" << std::endl; + return -1; + } + +#if 0 + std::cout << "[other] Testing Hashtable... "; std::cout.flush(); + { + Hashtable ht; + std::map ref; // assume std::map works correctly :) + for(int x=0;x<2;++x) { + for(int i=0;i<77777;++i) { + uint64_t k = rand(); + while ((k == 0)||(ref.count(k) > 0)) + ++k; + std::string v("!"); + for(int j=0;j<(int)(k % 64);++j) + v.push_back("0123456789"[rand() % 10]); + ref[k] = v; + ht.set(0xffffffffffffffffULL,v); + std::string &vref = ht[k]; + vref = v; + ht.erase(0xffffffffffffffffULL); + } + if (ht.size() != ref.size()) { + std::cout << "FAILED! (size mismatch, original)" << std::endl; + return -1; + } + { + Hashtable::Iterator i(ht); + uint64_t *k = (uint64_t *)0; + std::string *v = (std::string *)0; + while(i.next(k,v)) { + if (ref.find(*k)->second != *v) { + std::cout << "FAILED! (data mismatch!)" << std::endl; + return -1; + } + } + } + for(std::map::const_iterator i(ref.begin());i!=ref.end();++i) { + if (ht[i->first] != i->second) { + std::cout << "FAILED! (data mismatch!)" << std::endl; + return -1; + } + } + + Hashtable ht2; + ht2 = ht; + Hashtable ht3(ht2); + if (ht2.size() != ref.size()) { + std::cout << "FAILED! (size mismatch, assigned)" << std::endl; + return -1; + } + if (ht3.size() != ref.size()) { + std::cout << "FAILED! (size mismatch, copied)" << std::endl; + return -1; + } + + for(std::map::iterator i(ref.begin());i!=ref.end();++i) { + std::string *v = ht.get(i->first); + if (!v) { + std::cout << "FAILED! (key " << i->first << " not found, original)" << std::endl; + return -1; + } + if (*v != i->second) { + std::cout << "FAILED! (key " << i->first << " not equal, original)" << std::endl; + return -1; + } + v = ht2.get(i->first); + if (!v) { + std::cout << "FAILED! (key " << i->first << " not found, assigned)" << std::endl; + return -1; + } + if (*v != i->second) { + std::cout << "FAILED! (key " << i->first << " not equal, assigned)" << std::endl; + return -1; + } + v = ht3.get(i->first); + if (!v) { + std::cout << "FAILED! (key " << i->first << " not found, copied)" << std::endl; + return -1; + } + if (*v != i->second) { + std::cout << "FAILED! (key " << i->first << " not equal, copied)" << std::endl; + return -1; + } + } + { + uint64_t *k; + std::string *v; + Hashtable::Iterator i(ht); + unsigned long ic = 0; + while (i.next(k,v)) { + if (ref[*k] != *v) { + std::cout << "FAILED! (iterate)" << std::endl; + return -1; + } + ++ic; + } + if (ic != ht.size()) { + std::cout << "FAILED! (iterate coverage)" << std::endl; + return -1; + } + } + for(std::map::iterator i(ref.begin());i!=ref.end();) { + if (!ht.get(i->first)) { + std::cout << "FAILED! (erase, check if exists)" << std::endl; + return -1; + } + ht.erase(i->first); + if (ht.get(i->first)) { + std::cout << "FAILED! (erase, check if erased)" << std::endl; + return -1; + } + ref.erase(i++); + if (ht.size() != ref.size()) { + std::cout << "FAILED! (erase, size)" << std::endl; + return -1; + } + } + if (!ht.empty()) { + std::cout << "FAILED! (erase, empty)" << std::endl; + return -1; + } + for(int i=0;i<10000;++i) { + uint64_t k = rand(); + while ((k == 0)||(ref.count(k) > 0)) + ++k; + std::string v; + for(int j=0;j<(int)(k % 64);++j) + v.push_back("0123456789"[rand() % 10]); + ht.set(k,v); + ref[k] = v; + } + if (ht.size() != ref.size()) { + std::cout << "FAILED! (second populate)" << std::endl; + return -1; + } + ht.clear(); + ref.clear(); + if (ht.size() != ref.size()) { + std::cout << "FAILED! (clear)" << std::endl; + return -1; + } + for(int i=0;i<10000;++i) { + uint64_t k = rand(); + while ((k == 0)||(ref.count(k) > 0)) + ++k; + std::string v; + for(int j=0;j<(int)(k % 64);++j) + v.push_back("0123456789"[rand() % 10]); + ht.set(k,v); + ref[k] = v; + } + { + Hashtable::Iterator i(ht); + uint64_t *k; + std::string *v; + while (i.next(k,v)) + ht.erase(*k); + } + ref.clear(); + if (ht.size() != ref.size()) { + std::cout << "FAILED! (clear by iterate, " << ht.size() << ")" << std::endl; + return -1; + } + } + } + std::cout << "PASS" << std::endl; +#endif + + std::cout << "[other] Testing/fuzzing Dictionary... "; std::cout.flush(); + for(int k=0;k<1000;++k) { + Dictionary<8194> *test = new Dictionary<8194>(); + char key[32][16]; + char value[32][128]; + memset(key, 0, sizeof(key)); + memset(value, 0, sizeof(value)); + for(unsigned int q=0;q<32;++q) { + Utils::snprintf(key[q],16,"%.8lx",(unsigned long)(rand() % 1000) + (q * 1000)); + int r = rand() % 128; + for(int x=0;xadd(key[q],value[q],r); + } + for(unsigned int q=0;q<1024;++q) { + int r = rand() % 32; + char tmp[128]; + if (test->get(key[r],tmp,sizeof(tmp)) >= 0) { + if (strcmp(value[r],tmp)) { + std::cout << "FAILED (invalid value '" << value[r] << "' != '" << tmp << "')!" << std::endl; + return -1; + } + } else { + std::cout << "FAILED (can't find key '" << key[r] << "')!" << std::endl; + return -1; + } + } + delete test; + } + int foo = 0; + volatile int *volatile bar = &foo; // force compiler not to optimize out test.get() below + for(int k=0;k<200;++k) { + int r = rand() % 8194; + unsigned char *tmp = new unsigned char[8194]; + for(int q=0;q *test = new Dictionary<8194>((const char *)tmp); + for(unsigned int q=0;q<100;++q) { + char tmp[128]; + for(unsigned int x=0;x<128;++x) + tmp[x] = (char)(rand() & 0xff); + tmp[127] = (char)0; + char value[8194]; + *bar += test->get(tmp,value,sizeof(value)); + } + delete test; + delete[] tmp; + } + std::cout << "PASS (junk value to prevent optimization-out of test: " << foo << ")" << std::endl; + + return 0; +} + +#define ZT_TEST_PHY_NUM_UDP_PACKETS 10000 +#define ZT_TEST_PHY_UDP_PACKET_SIZE 1000 +#define ZT_TEST_PHY_NUM_VALID_TCP_CONNECTS 10 +#define ZT_TEST_PHY_NUM_INVALID_TCP_CONNECTS 2 +#define ZT_TEST_PHY_TCP_MESSAGE_SIZE 1000000 +#define ZT_TEST_PHY_TIMEOUT_MS 20000 +static unsigned long phyTestUdpPacketCount = 0; +static unsigned long phyTestTcpByteCount = 0; +static unsigned long phyTestTcpConnectSuccessCount = 0; +static unsigned long phyTestTcpConnectFailCount = 0; +static unsigned long phyTestTcpAcceptCount = 0; +struct TestPhyHandlers; +static Phy *testPhyInstance = (Phy *)0; +struct TestPhyHandlers +{ + inline void phyOnDatagram(PhySocket *sock,void **uptr,const struct sockaddr *localAddr,const struct sockaddr *from,void *data,unsigned long len) + { + ++phyTestUdpPacketCount; + } + + inline void phyOnTcpConnect(PhySocket *sock,void **uptr,bool success) + { + if (success) { + ++phyTestTcpConnectSuccessCount; + } else { + ++phyTestTcpConnectFailCount; + } + } + + inline void phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from) + { + ++phyTestTcpAcceptCount; + *uptrN = new std::string(ZT_TEST_PHY_TCP_MESSAGE_SIZE,(char)0xff); + testPhyInstance->setNotifyWritable(sockN,true); + } + + inline void phyOnTcpClose(PhySocket *sock,void **uptr) + { + delete (std::string *)*uptr; // delete testMessage if any + } + + inline void phyOnTcpData(PhySocket *sock,void **uptr,void *data,unsigned long len) + { + phyTestTcpByteCount += len; + } + + inline void phyOnTcpWritable(PhySocket *sock,void **uptr) + { + std::string *testMessage = (std::string *)*uptr; + if ((testMessage)&&(testMessage->length() > 0)) { + long sent = testPhyInstance->streamSend(sock,(const void *)testMessage->data(),(unsigned long)testMessage->length(),true); + if (sent > 0) + testMessage->erase(0,sent); + } + if ((!testMessage)||(!testMessage->length())) { + testPhyInstance->close(sock,true); + } + } + +#ifdef __UNIX_LIKE__ + inline void phyOnUnixAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN) {} + inline void phyOnUnixClose(PhySocket *sock,void **uptr) {} + inline void phyOnUnixData(PhySocket *sock,void **uptr,void *data,unsigned long len) {} + inline void phyOnUnixWritable(PhySocket *sock,void **uptr,bool b) {} +#endif // __UNIX_LIKE__ + + inline void phyOnFileDescriptorActivity(PhySocket *sock,void **uptr,bool readable,bool writable) {} +}; +static int testPhy() +{ + char udpTestPayload[ZT_TEST_PHY_UDP_PACKET_SIZE]; + memset(udpTestPayload,0xff,sizeof(udpTestPayload)); + + struct sockaddr_in bindaddr; + memset(&bindaddr,0,sizeof(bindaddr)); + bindaddr.sin_family = AF_INET; + bindaddr.sin_port = Utils::hton((uint16_t)60002); + bindaddr.sin_addr.s_addr = Utils::hton((uint32_t)0x7f000001); + struct sockaddr_in invalidAddr; + memset(&bindaddr,0,sizeof(bindaddr)); + bindaddr.sin_family = AF_INET; + bindaddr.sin_port = Utils::hton((uint16_t)60004); + bindaddr.sin_addr.s_addr = Utils::hton((uint32_t)0x7f000001); + + std::cout << "[phy] Creating phy endpoint..." << std::endl; + TestPhyHandlers testPhyHandlers; + testPhyInstance = new Phy(&testPhyHandlers,false,true); + + std::cout << "[phy] Binding UDP listen socket to 127.0.0.1/60002... "; + PhySocket *udpListenSock = testPhyInstance->udpBind((const struct sockaddr *)&bindaddr); + if (!udpListenSock) { + std::cout << "FAILED." << std::endl; + return -1; + } + std::cout << "OK" << std::endl; + + std::cout << "[phy] Binding TCP listen socket to 127.0.0.1/60002... "; + PhySocket *tcpListenSock = testPhyInstance->tcpListen((const struct sockaddr *)&bindaddr); + if (!tcpListenSock) { + std::cout << "FAILED." << std::endl; + return -1; + } + std::cout << "OK" << std::endl; + + unsigned long phyTestUdpPacketsSent = 0; + unsigned long phyTestTcpValidConnectionsAttempted = 0; + unsigned long phyTestTcpInvalidConnectionsAttempted = 0; + + std::cout << "[phy] Testing UDP send/receive... "; std::cout.flush(); + uint64_t timeoutAt = OSUtils::now() + ZT_TEST_PHY_TIMEOUT_MS; + while ((OSUtils::now() < timeoutAt)&&(phyTestUdpPacketCount < ZT_TEST_PHY_NUM_UDP_PACKETS)) { + if (phyTestUdpPacketsSent < ZT_TEST_PHY_NUM_UDP_PACKETS) { + if (!testPhyInstance->udpSend(udpListenSock,(const struct sockaddr *)&bindaddr,udpTestPayload,sizeof(udpTestPayload))) { + std::cout << "FAILED." << std::endl; + return -1; + } else ++phyTestUdpPacketsSent; + } + testPhyInstance->poll(100); + } + std::cout << "got " << phyTestUdpPacketCount << " packets, OK" << std::endl; + + std::cout << "[phy] Testing TCP... "; std::cout.flush(); + timeoutAt = OSUtils::now() + ZT_TEST_PHY_TIMEOUT_MS; + while ((OSUtils::now() < timeoutAt)&&(phyTestTcpByteCount < (ZT_TEST_PHY_NUM_VALID_TCP_CONNECTS * ZT_TEST_PHY_TCP_MESSAGE_SIZE))) { + if (phyTestTcpValidConnectionsAttempted < ZT_TEST_PHY_NUM_VALID_TCP_CONNECTS) { + ++phyTestTcpValidConnectionsAttempted; + bool connected = false; + if (!testPhyInstance->tcpConnect((const struct sockaddr *)&bindaddr,connected,(void *)0,true)) + ++phyTestTcpConnectFailCount; + } + if (phyTestTcpInvalidConnectionsAttempted < ZT_TEST_PHY_NUM_INVALID_TCP_CONNECTS) { + ++phyTestTcpInvalidConnectionsAttempted; + bool connected = false; + if (!testPhyInstance->tcpConnect((const struct sockaddr *)&invalidAddr,connected,(void *)0,true)) + ++phyTestTcpConnectFailCount; + } + testPhyInstance->poll(100); + } + if (phyTestTcpByteCount < (ZT_TEST_PHY_NUM_VALID_TCP_CONNECTS * ZT_TEST_PHY_TCP_MESSAGE_SIZE)) { + std::cout << "got " << phyTestTcpConnectSuccessCount << " connect successes, " << phyTestTcpConnectFailCount << " failures, and " << phyTestTcpByteCount << " bytes, FAILED." << std::endl; + return -1; + } else { + std::cout << "got " << phyTestTcpConnectSuccessCount << " connect successes, " << phyTestTcpConnectFailCount << " failures, and " << phyTestTcpByteCount << " bytes, OK" << std::endl; + } + + return 0; +} + +/* +static int testHttp() +{ + std::map requestHeaders,responseHeaders; + std::string responseBody; + + InetAddress downloadZerotierDotCom; + std::vector rr(OSUtils::resolve("download.zerotier.com")); + if (rr.empty()) { + std::cout << "[http] Resolve of download.zerotier.com failed, skipping." << std::endl; + return 0; + } else { + for(std::vector::iterator r(rr.begin());r!=rr.end();++r) { + std::cout << "[http] download.zerotier.com: " << r->toString() << std::endl; + if (r->isV4()) + downloadZerotierDotCom = *r; + } + } + downloadZerotierDotCom.setPort(80); + + std::cout << "[http] GET http://download.zerotier.com/dev/1k @" << downloadZerotierDotCom.toString() << " ... "; std::cout.flush(); + requestHeaders["Host"] = "download.zerotier.com"; + unsigned int sc = Http::GET(1024 * 1024 * 16,60000,reinterpret_cast(&downloadZerotierDotCom),"/dev/1k",requestHeaders,responseHeaders,responseBody); + std::cout << sc << " " << responseBody.length() << " bytes "; + if (sc == 0) + std::cout << "ERROR: " << responseBody << std::endl; + else std::cout << "DONE" << std::endl; + + std::cout << "[http] GET http://download.zerotier.com/dev/4m @" << downloadZerotierDotCom.toString() << " ... "; std::cout.flush(); + requestHeaders["Host"] = "download.zerotier.com"; + sc = Http::GET(1024 * 1024 * 16,60000,reinterpret_cast(&downloadZerotierDotCom),"/dev/4m",requestHeaders,responseHeaders,responseBody); + std::cout << sc << " " << responseBody.length() << " bytes "; + if (sc == 0) + std::cout << "ERROR: " << responseBody << std::endl; + else std::cout << "DONE" << std::endl; + + downloadZerotierDotCom = InetAddress("1.0.0.1/1234"); + std::cout << "[http] GET @" << downloadZerotierDotCom.toString() << " ... "; std::cout.flush(); + sc = Http::GET(1024 * 1024 * 16,2500,reinterpret_cast(&downloadZerotierDotCom),"/dev/4m",requestHeaders,responseHeaders,responseBody); + std::cout << sc << " (should be 0, time out)" << std::endl; + + return 0; +} +*/ + +#ifdef __WINDOWS__ +int __cdecl _tmain(int argc, _TCHAR* argv[]) +#else +int main(int argc,char **argv) +#endif +{ + int r = 0; + +#ifdef __WINDOWS__ + WSADATA wsaData; + WSAStartup(MAKEWORD(2,2),&wsaData); +#endif + + // Code to generate the C25519 test vectors -- did this once and then + // put these up top so that we can ensure that every platform produces + // the same result. + /* + for(int k=0;k<32;++k) { + C25519::Pair p1 = C25519::generate(); + C25519::Pair p2 = C25519::generate(); + unsigned char agg[64]; + C25519::agree(p1,p2.pub,agg,64); + C25519::Signature sig1 = C25519::sign(p1,agg,64); + C25519::Signature sig2 = C25519::sign(p2,agg,64); + printf("{{"); + for(int i=0;i<64;++i) + printf("%s0x%.2x",((i > 0) ? "," : ""),(unsigned int)p1.pub.data[i]); + printf("},{"); + for(int i=0;i<64;++i) + printf("%s0x%.2x",((i > 0) ? "," : ""),(unsigned int)p1.priv.data[i]); + printf("},{"); + for(int i=0;i<64;++i) + printf("%s0x%.2x",((i > 0) ? "," : ""),(unsigned int)p2.pub.data[i]); + printf("},{"); + for(int i=0;i<64;++i) + printf("%s0x%.2x",((i > 0) ? "," : ""),(unsigned int)p2.priv.data[i]); + printf("},{"); + for(int i=0;i<64;++i) + printf("%s0x%.2x",((i > 0) ? "," : ""),(unsigned int)agg[i]); + printf("},{"); + for(int i=0;i<96;++i) + printf("%s0x%.2x",((i > 0) ? "," : ""),(unsigned int)sig1.data[i]); + printf("},{"); + for(int i=0;i<96;++i) + printf("%s0x%.2x",((i > 0) ? "," : ""),(unsigned int)sig2.data[i]); + printf("}}\n"); + } + exit(0); + */ + + std::cout << "[info] sizeof(void *) == " << sizeof(void *) << std::endl; + std::cout << "[info] sizeof(NetworkConfig) == " << sizeof(ZeroTier::NetworkConfig) << std::endl; + + srand((unsigned int)time(0)); + + ///* + r |= testOther(); + r |= testCrypto(); + r |= testPacket(); + r |= testIdentity(); + r |= testCertificate(); + r |= testPhy(); + //r |= testHttp(); + //*/ + + if (r) + std::cout << std::endl << "SOMETHING FAILED!" << std::endl; + + /* +#ifdef ZT_USE_MINIUPNPC + std::cout << std::endl; + std::cout << "[portmapper] Starting port mapper and waiting forever... use CTRL+C to exit. (enable ZT_PORTMAPPER_TRACE in PortMapper.cpp for output)" << std::endl; + PortMapper mapper(12345,"ZeroTier/__selftest"); + Thread::sleep(0xffffffff); +#endif + */ + + return r; +} diff --git a/service/ClusterDefinition.hpp b/service/ClusterDefinition.hpp new file mode 100644 index 0000000..dda1a8c --- /dev/null +++ b/service/ClusterDefinition.hpp @@ -0,0 +1,160 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_CLUSTERDEFINITION_HPP +#define ZT_CLUSTERDEFINITION_HPP + +#ifdef ZT_ENABLE_CLUSTER + +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Utils.hpp" +#include "../node/NonCopyable.hpp" +#include "../osdep/OSUtils.hpp" + +#include "ClusterGeoIpService.hpp" + +namespace ZeroTier { + +/** + * Parser for cluster definition file + */ +class ClusterDefinition : NonCopyable +{ +public: + struct MemberDefinition + { + MemberDefinition() : id(0),x(0),y(0),z(0) { name[0] = (char)0; } + + unsigned int id; + int x,y,z; + char name[256]; + InetAddress clusterEndpoint; + std::vector zeroTierEndpoints; + }; + + /** + * Load and initialize cluster definition and GeoIP data if any + * + * @param myAddress My ZeroTier address + * @param pathToClusterFile Path to cluster definition file + * @throws std::runtime_error Invalid cluster definition or unable to load data + */ + ClusterDefinition(uint64_t myAddress,const char *pathToClusterFile) + { + std::string cf; + if (!OSUtils::readFile(pathToClusterFile,cf)) + return; + + char myAddressStr[64]; + Utils::snprintf(myAddressStr,sizeof(myAddressStr),"%.10llx",myAddress); + + std::vector lines(OSUtils::split(cf.c_str(),"\r\n","","")); + for(std::vector::iterator l(lines.begin());l!=lines.end();++l) { + std::vector fields(OSUtils::split(l->c_str()," \t","","")); + if ((fields.size() < 5)||(fields[0][0] == '#')||(fields[0] != myAddressStr)) + continue; + + //
geo + if (fields[1] == "geo") { + if ((fields.size() >= 7)&&(OSUtils::fileExists(fields[2].c_str()))) { + int ipStartColumn = Utils::strToInt(fields[3].c_str()); + int ipEndColumn = Utils::strToInt(fields[4].c_str()); + int latitudeColumn = Utils::strToInt(fields[5].c_str()); + int longitudeColumn = Utils::strToInt(fields[6].c_str()); + if (_geo.load(fields[2].c_str(),ipStartColumn,ipEndColumn,latitudeColumn,longitudeColumn) <= 0) + throw std::runtime_error(std::string("failed to load geo-ip data from ")+fields[2]); + } + continue; + } + + //
+ int id = Utils::strToUInt(fields[1].c_str()); + if ((id < 0)||(id > ZT_CLUSTER_MAX_MEMBERS)) + throw std::runtime_error(std::string("invalid cluster member ID: ")+fields[1]); + MemberDefinition &md = _md[id]; + + md.id = (unsigned int)id; + if (fields.size() >= 6) { + std::vector xyz(OSUtils::split(fields[5].c_str(),",","","")); + md.x = (xyz.size() > 0) ? Utils::strToInt(xyz[0].c_str()) : 0; + md.y = (xyz.size() > 1) ? Utils::strToInt(xyz[1].c_str()) : 0; + md.z = (xyz.size() > 2) ? Utils::strToInt(xyz[2].c_str()) : 0; + } + Utils::scopy(md.name,sizeof(md.name),fields[2].c_str()); + md.clusterEndpoint.fromString(fields[3]); + if (!md.clusterEndpoint) + continue; + std::vector zips(OSUtils::split(fields[4].c_str(),",","","")); + for(std::vector::iterator zip(zips.begin());zip!=zips.end();++zip) { + InetAddress i; + i.fromString(*zip); + if (i) + md.zeroTierEndpoints.push_back(i); + } + + _ids.push_back((unsigned int)id); + } + + std::sort(_ids.begin(),_ids.end()); + } + + /** + * @return All member definitions in this cluster by ID (ID is array index) + */ + inline const MemberDefinition &operator[](unsigned int id) const throw() { return _md[id]; } + + /** + * @return Number of members in this cluster + */ + inline unsigned int size() const throw() { return (unsigned int)_ids.size(); } + + /** + * @return IDs of members in this cluster sorted by ID + */ + inline const std::vector &ids() const throw() { return _ids; } + + /** + * @return GeoIP service for this cluster + */ + inline ClusterGeoIpService &geo() throw() { return _geo; } + + /** + * @return A vector (new copy) containing all cluster members + */ + inline std::vector members() const + { + std::vector m; + for(std::vector::const_iterator i(_ids.begin());i!=_ids.end();++i) + m.push_back(_md[*i]); + return m; + } + +private: + MemberDefinition _md[ZT_CLUSTER_MAX_MEMBERS]; + std::vector _ids; + ClusterGeoIpService _geo; +}; + +} // namespace ZeroTier + +#endif // ZT_ENABLE_CLUSTER + +#endif diff --git a/service/ClusterGeoIpService.cpp b/service/ClusterGeoIpService.cpp new file mode 100644 index 0000000..89015c5 --- /dev/null +++ b/service/ClusterGeoIpService.cpp @@ -0,0 +1,235 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifdef ZT_ENABLE_CLUSTER + +#include + +#include + +#include "ClusterGeoIpService.hpp" + +#include "../node/Utils.hpp" +#include "../osdep/OSUtils.hpp" + +#define ZT_CLUSTERGEOIPSERVICE_FILE_MODIFICATION_CHECK_EVERY 10000 + +namespace ZeroTier { + +ClusterGeoIpService::ClusterGeoIpService() : + _pathToCsv(), + _ipStartColumn(-1), + _ipEndColumn(-1), + _latitudeColumn(-1), + _longitudeColumn(-1), + _lastFileCheckTime(0), + _csvModificationTime(0), + _csvFileSize(0) +{ +} + +ClusterGeoIpService::~ClusterGeoIpService() +{ +} + +bool ClusterGeoIpService::locate(const InetAddress &ip,int &x,int &y,int &z) +{ + Mutex::Lock _l(_lock); + + if ((_pathToCsv.length() > 0)&&((OSUtils::now() - _lastFileCheckTime) > ZT_CLUSTERGEOIPSERVICE_FILE_MODIFICATION_CHECK_EVERY)) { + _lastFileCheckTime = OSUtils::now(); + if ((_csvFileSize != OSUtils::getFileSize(_pathToCsv.c_str()))||(_csvModificationTime != OSUtils::getLastModified(_pathToCsv.c_str()))) + _load(_pathToCsv.c_str(),_ipStartColumn,_ipEndColumn,_latitudeColumn,_longitudeColumn); + } + + /* We search by looking up the upper bound of the sorted vXdb vectors + * and then iterating down for a matching IP range. We stop when we hit + * the beginning or an entry whose start and end are before the IP we + * are searching. */ + + if ((ip.ss_family == AF_INET)&&(_v4db.size() > 0)) { + _V4E key; + key.start = Utils::ntoh((uint32_t)(reinterpret_cast(&ip)->sin_addr.s_addr)); + std::vector<_V4E>::const_iterator i(std::upper_bound(_v4db.begin(),_v4db.end(),key)); + while (i != _v4db.begin()) { + --i; + if ((key.start >= i->start)&&(key.start <= i->end)) { + x = i->x; + y = i->y; + z = i->z; + //printf("%s : %f,%f %d,%d,%d\n",ip.toIpString().c_str(),i->lat,i->lon,x,y,z); + return true; + } else if ((key.start > i->start)&&(key.start > i->end)) + break; + } + } else if ((ip.ss_family == AF_INET6)&&(_v6db.size() > 0)) { + _V6E key; + memcpy(key.start,reinterpret_cast(&ip)->sin6_addr.s6_addr,16); + std::vector<_V6E>::const_iterator i(std::upper_bound(_v6db.begin(),_v6db.end(),key)); + while (i != _v6db.begin()) { + --i; + const int s_vs_s = memcmp(key.start,i->start,16); + const int s_vs_e = memcmp(key.start,i->end,16); + if ((s_vs_s >= 0)&&(s_vs_e <= 0)) { + x = i->x; + y = i->y; + z = i->z; + //printf("%s : %f,%f %d,%d,%d\n",ip.toIpString().c_str(),i->lat,i->lon,x,y,z); + return true; + } else if ((s_vs_s > 0)&&(s_vs_e > 0)) + break; + } + } + + return false; +} + +void ClusterGeoIpService::_parseLine(const char *line,std::vector<_V4E> &v4db,std::vector<_V6E> &v6db,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn) +{ + std::vector ls(OSUtils::split(line,",\t","\\","\"'")); + if ( ((ipStartColumn >= 0)&&(ipStartColumn < (int)ls.size()))&& + ((ipEndColumn >= 0)&&(ipEndColumn < (int)ls.size()))&& + ((latitudeColumn >= 0)&&(latitudeColumn < (int)ls.size()))&& + ((longitudeColumn >= 0)&&(longitudeColumn < (int)ls.size())) ) { + InetAddress ipStart(ls[ipStartColumn].c_str(),0); + InetAddress ipEnd(ls[ipEndColumn].c_str(),0); + const double lat = strtod(ls[latitudeColumn].c_str(),(char **)0); + const double lon = strtod(ls[longitudeColumn].c_str(),(char **)0); + + if ((ipStart.ss_family == ipEnd.ss_family)&&(ipStart)&&(ipEnd)&&(std::isfinite(lat))&&(std::isfinite(lon))) { + const double latRadians = lat * 0.01745329251994; // PI / 180 + const double lonRadians = lon * 0.01745329251994; // PI / 180 + const double cosLat = cos(latRadians); + const int x = (int)round((-6371.0) * cosLat * cos(lonRadians)); // 6371 == Earth's approximate radius in kilometers + const int y = (int)round(6371.0 * sin(latRadians)); + const int z = (int)round(6371.0 * cosLat * sin(lonRadians)); + + if (ipStart.ss_family == AF_INET) { + v4db.push_back(_V4E()); + v4db.back().start = Utils::ntoh((uint32_t)(reinterpret_cast(&ipStart)->sin_addr.s_addr)); + v4db.back().end = Utils::ntoh((uint32_t)(reinterpret_cast(&ipEnd)->sin_addr.s_addr)); + v4db.back().lat = (float)lat; + v4db.back().lon = (float)lon; + v4db.back().x = x; + v4db.back().y = y; + v4db.back().z = z; + //printf("%s - %s : %d,%d,%d\n",ipStart.toIpString().c_str(),ipEnd.toIpString().c_str(),x,y,z); + } else if (ipStart.ss_family == AF_INET6) { + v6db.push_back(_V6E()); + memcpy(v6db.back().start,reinterpret_cast(&ipStart)->sin6_addr.s6_addr,16); + memcpy(v6db.back().end,reinterpret_cast(&ipEnd)->sin6_addr.s6_addr,16); + v6db.back().lat = (float)lat; + v6db.back().lon = (float)lon; + v6db.back().x = x; + v6db.back().y = y; + v6db.back().z = z; + //printf("%s - %s : %d,%d,%d\n",ipStart.toIpString().c_str(),ipEnd.toIpString().c_str(),x,y,z); + } + } + } +} + +long ClusterGeoIpService::_load(const char *pathToCsv,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn) +{ + // assumes _lock is locked + + FILE *f = fopen(pathToCsv,"rb"); + if (!f) + return -1; + + std::vector<_V4E> v4db; + std::vector<_V6E> v6db; + v4db.reserve(16777216); + v6db.reserve(16777216); + + char buf[4096]; + char linebuf[1024]; + unsigned int lineptr = 0; + for(;;) { + int n = (int)fread(buf,1,sizeof(buf),f); + if (n <= 0) + break; + for(int i=0;i 0)||(v6db.size() > 0)) { + std::sort(v4db.begin(),v4db.end()); + std::sort(v6db.begin(),v6db.end()); + + _pathToCsv = pathToCsv; + _ipStartColumn = ipStartColumn; + _ipEndColumn = ipEndColumn; + _latitudeColumn = latitudeColumn; + _longitudeColumn = longitudeColumn; + + _lastFileCheckTime = OSUtils::now(); + _csvModificationTime = OSUtils::getLastModified(pathToCsv); + _csvFileSize = OSUtils::getFileSize(pathToCsv); + + _v4db.swap(v4db); + _v6db.swap(v6db); + + return (long)(_v4db.size() + _v6db.size()); + } else { + return 0; + } +} + +} // namespace ZeroTier + +#endif // ZT_ENABLE_CLUSTER + +/* +int main(int argc,char **argv) +{ + char buf[1024]; + + ZeroTier::ClusterGeoIpService gip; + printf("loading...\n"); + gip.load("/Users/api/Code/ZeroTier/Infrastructure/root-servers/zerotier-one/cluster-geoip.csv",0,1,5,6); + printf("... done!\n"); fflush(stdout); + + while (gets(buf)) { // unsafe, testing only + ZeroTier::InetAddress addr(buf,0); + printf("looking up: %s\n",addr.toString().c_str()); fflush(stdout); + int x = 0,y = 0,z = 0; + if (gip.locate(addr,x,y,z)) { + //printf("%s: %d,%d,%d\n",addr.toString().c_str(),x,y,z); fflush(stdout); + } else { + printf("%s: not found!\n",addr.toString().c_str()); fflush(stdout); + } + } + + return 0; +} +*/ diff --git a/service/ClusterGeoIpService.hpp b/service/ClusterGeoIpService.hpp new file mode 100644 index 0000000..ff2fcdb --- /dev/null +++ b/service/ClusterGeoIpService.hpp @@ -0,0 +1,143 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_CLUSTERGEOIPSERVICE_HPP +#define ZT_CLUSTERGEOIPSERVICE_HPP + +#ifdef ZT_ENABLE_CLUSTER + +#include +#include +#include +#include + +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../node/Mutex.hpp" +#include "../node/NonCopyable.hpp" +#include "../node/InetAddress.hpp" + +namespace ZeroTier { + +/** + * Loads a GeoIP CSV into memory for fast lookup, reloading as needed + * + * This was designed around the CSV from https://db-ip.com but can be used + * with any similar GeoIP CSV database that is presented in the form of an + * IP range and lat/long coordinates. + * + * It loads the whole database into memory, which can be kind of large. If + * the CSV file changes, the changes are loaded automatically. + */ +class ClusterGeoIpService : NonCopyable +{ +public: + ClusterGeoIpService(); + ~ClusterGeoIpService(); + + /** + * Load or reload CSV file + * + * CSV column indexes start at zero. CSVs can be quoted with single or + * double quotes. Whitespace before or after commas is ignored. Backslash + * may be used for escaping whitespace as well. + * + * @param pathToCsv Path to (uncompressed) CSV file + * @param ipStartColumn Column with IP range start + * @param ipEndColumn Column with IP range end (inclusive) + * @param latitudeColumn Column with latitude + * @param longitudeColumn Column with longitude + * @return Number of valid records loaded or -1 on error (invalid file, not found, etc.) + */ + inline long load(const char *pathToCsv,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn) + { + Mutex::Lock _l(_lock); + return _load(pathToCsv,ipStartColumn,ipEndColumn,latitudeColumn,longitudeColumn); + } + + /** + * Attempt to locate an IP + * + * This returns true if x, y, and z are set. If the return value is false + * the values of x, y, and z are undefined. + * + * @param ip IPv4 or IPv6 address + * @param x Reference to variable to receive X + * @param y Reference to variable to receive Y + * @param z Reference to variable to receive Z + * @return True if coordinates were set + */ + bool locate(const InetAddress &ip,int &x,int &y,int &z); + + /** + * @return True if IP database/service is available for queries (otherwise locate() will always be false) + */ + inline bool available() const + { + Mutex::Lock _l(_lock); + return ((_v4db.size() + _v6db.size()) > 0); + } + +private: + struct _V4E + { + uint32_t start; + uint32_t end; + float lat,lon; + int16_t x,y,z; + + inline bool operator<(const _V4E &e) const { return (start < e.start); } + }; + + struct _V6E + { + uint8_t start[16]; + uint8_t end[16]; + float lat,lon; + int16_t x,y,z; + + inline bool operator<(const _V6E &e) const { return (memcmp(start,e.start,16) < 0); } + }; + + static void _parseLine(const char *line,std::vector<_V4E> &v4db,std::vector<_V6E> &v6db,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn); + long _load(const char *pathToCsv,int ipStartColumn,int ipEndColumn,int latitudeColumn,int longitudeColumn); + + std::string _pathToCsv; + int _ipStartColumn; + int _ipEndColumn; + int _latitudeColumn; + int _longitudeColumn; + + uint64_t _lastFileCheckTime; + uint64_t _csvModificationTime; + int64_t _csvFileSize; + + std::vector<_V4E> _v4db; + std::vector<_V6E> _v6db; + + Mutex _lock; +}; + +} // namespace ZeroTier + +#endif // ZT_ENABLE_CLUSTER + +#endif diff --git a/service/OneService.cpp b/service/OneService.cpp new file mode 100644 index 0000000..b151d25 --- /dev/null +++ b/service/OneService.cpp @@ -0,0 +1,2588 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../version.h" +#include "../include/ZeroTierOne.h" + +#include "../node/Constants.hpp" +#include "../node/Mutex.hpp" +#include "../node/Node.hpp" +#include "../node/Utils.hpp" +#include "../node/InetAddress.hpp" +#include "../node/MAC.hpp" +#include "../node/Identity.hpp" +#include "../node/World.hpp" + +#include "../osdep/Phy.hpp" +#include "../osdep/Thread.hpp" +#include "../osdep/OSUtils.hpp" +#include "../osdep/Http.hpp" +#include "../osdep/PortMapper.hpp" +#include "../osdep/Binder.hpp" +#include "../osdep/ManagedRoute.hpp" + +#include "OneService.hpp" +#include "ClusterGeoIpService.hpp" +#include "ClusterDefinition.hpp" +#include "SoftwareUpdater.hpp" + +#ifdef __WINDOWS__ +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#ifdef ZT_USE_SYSTEM_HTTP_PARSER +#include +#else +#include "../ext/http-parser/http_parser.h" +#endif + +#include "../ext/json/json.hpp" + +using json = nlohmann::json; + +/** + * Uncomment to enable UDP breakage switch + * + * If this is defined, the presence of a file called /tmp/ZT_BREAK_UDP + * will cause direct UDP TX/RX to stop working. This can be used to + * test TCP tunneling fallback and other robustness features. Deleting + * this file will cause it to start working again. + */ +//#define ZT_BREAK_UDP + +#include "../controller/EmbeddedNetworkController.hpp" + +// Include the right tap device driver for this platform -- add new platforms here +#ifdef ZT_SERVICE_NETCON + +// In network containers builds, use the virtual netcon endpoint instead of a tun/tap port driver +#include "../netcon/NetconEthernetTap.hpp" +namespace ZeroTier { typedef NetconEthernetTap EthernetTap; } + +#else // not ZT_SERVICE_NETCON so pick a tap driver + +#ifdef __APPLE__ +#include "../osdep/OSXEthernetTap.hpp" +namespace ZeroTier { typedef OSXEthernetTap EthernetTap; } +#endif // __APPLE__ +#ifdef __LINUX__ +#include "../osdep/LinuxEthernetTap.hpp" +namespace ZeroTier { typedef LinuxEthernetTap EthernetTap; } +#endif // __LINUX__ +#ifdef __WINDOWS__ +#include "../osdep/WindowsEthernetTap.hpp" +namespace ZeroTier { typedef WindowsEthernetTap EthernetTap; } +#endif // __WINDOWS__ +#ifdef __FreeBSD__ +#include "../osdep/BSDEthernetTap.hpp" +namespace ZeroTier { typedef BSDEthernetTap EthernetTap; } +#endif // __FreeBSD__ +#ifdef __OpenBSD__ +#include "../osdep/BSDEthernetTap.hpp" +namespace ZeroTier { typedef BSDEthernetTap EthernetTap; } +#endif // __OpenBSD__ + +#endif // ZT_SERVICE_NETCON + +// Sanity limits for HTTP +#define ZT_MAX_HTTP_MESSAGE_SIZE (1024 * 1024 * 64) +#define ZT_MAX_HTTP_CONNECTIONS 64 + +// Interface metric for ZeroTier taps -- this ensures that if we are on WiFi and also +// bridged via ZeroTier to the same LAN traffic will (if the OS is sane) prefer WiFi. +#define ZT_IF_METRIC 5000 + +// How often to check for new multicast subscriptions on a tap device +#define ZT_TAP_CHECK_MULTICAST_INTERVAL 5000 + +// Path under ZT1 home for controller database if controller is enabled +#define ZT_CONTROLLER_DB_PATH "controller.d" + +// TCP fallback relay (run by ZeroTier, Inc. -- this will eventually go away) +#define ZT_TCP_FALLBACK_RELAY "204.80.128.1/443" + +// Frequency at which we re-resolve the TCP fallback relay +#define ZT_TCP_FALLBACK_RERESOLVE_DELAY 86400000 + +// Attempt to engage TCP fallback after this many ms of no reply to packets sent to global-scope IPs +#define ZT_TCP_FALLBACK_AFTER 60000 + +// How often to check for local interface addresses +#define ZT_LOCAL_INTERFACE_CHECK_INTERVAL 60000 + +// Clean files from iddb.d that are older than this (60 days) +#define ZT_IDDB_CLEANUP_AGE 5184000000ULL + +namespace ZeroTier { + +namespace { + +static std::string _trimString(const std::string &s) +{ + unsigned long end = (unsigned long)s.length(); + while (end) { + char c = s[end - 1]; + if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t')) + --end; + else break; + } + unsigned long start = 0; + while (start < end) { + char c = s[start]; + if ((c == ' ')||(c == '\r')||(c == '\n')||(!c)||(c == '\t')) + ++start; + else break; + } + return s.substr(start,end - start); +} + +static void _networkToJson(nlohmann::json &nj,const ZT_VirtualNetworkConfig *nc,const std::string &portDeviceName,const OneService::NetworkSettings &localSettings) +{ + char tmp[256]; + + const char *nstatus = "",*ntype = ""; + switch(nc->status) { + case ZT_NETWORK_STATUS_REQUESTING_CONFIGURATION: nstatus = "REQUESTING_CONFIGURATION"; break; + case ZT_NETWORK_STATUS_OK: nstatus = "OK"; break; + case ZT_NETWORK_STATUS_ACCESS_DENIED: nstatus = "ACCESS_DENIED"; break; + case ZT_NETWORK_STATUS_NOT_FOUND: nstatus = "NOT_FOUND"; break; + case ZT_NETWORK_STATUS_PORT_ERROR: nstatus = "PORT_ERROR"; break; + case ZT_NETWORK_STATUS_CLIENT_TOO_OLD: nstatus = "CLIENT_TOO_OLD"; break; + } + switch(nc->type) { + case ZT_NETWORK_TYPE_PRIVATE: ntype = "PRIVATE"; break; + case ZT_NETWORK_TYPE_PUBLIC: ntype = "PUBLIC"; break; + } + + Utils::snprintf(tmp,sizeof(tmp),"%.16llx",nc->nwid); + nj["id"] = tmp; + nj["nwid"] = tmp; + Utils::snprintf(tmp,sizeof(tmp),"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",(unsigned int)((nc->mac >> 40) & 0xff),(unsigned int)((nc->mac >> 32) & 0xff),(unsigned int)((nc->mac >> 24) & 0xff),(unsigned int)((nc->mac >> 16) & 0xff),(unsigned int)((nc->mac >> 8) & 0xff),(unsigned int)(nc->mac & 0xff)); + nj["mac"] = tmp; + nj["name"] = nc->name; + nj["status"] = nstatus; + nj["type"] = ntype; + nj["mtu"] = nc->mtu; + nj["dhcp"] = (bool)(nc->dhcp != 0); + nj["bridge"] = (bool)(nc->bridge != 0); + nj["broadcastEnabled"] = (bool)(nc->broadcastEnabled != 0); + nj["portError"] = nc->portError; + nj["netconfRevision"] = nc->netconfRevision; + nj["portDeviceName"] = portDeviceName; + nj["allowManaged"] = localSettings.allowManaged; + nj["allowGlobal"] = localSettings.allowGlobal; + nj["allowDefault"] = localSettings.allowDefault; + + nlohmann::json aa = nlohmann::json::array(); + for(unsigned int i=0;iassignedAddressCount;++i) { + aa.push_back(reinterpret_cast(&(nc->assignedAddresses[i]))->toString()); + } + nj["assignedAddresses"] = aa; + + nlohmann::json ra = nlohmann::json::array(); + for(unsigned int i=0;irouteCount;++i) { + nlohmann::json rj; + rj["target"] = reinterpret_cast(&(nc->routes[i].target))->toString(); + if (nc->routes[i].via.ss_family == nc->routes[i].target.ss_family) + rj["via"] = reinterpret_cast(&(nc->routes[i].via))->toIpString(); + else rj["via"] = nlohmann::json(); + rj["flags"] = (int)nc->routes[i].flags; + rj["metric"] = (int)nc->routes[i].metric; + ra.push_back(rj); + } + nj["routes"] = ra; +} + +static void _peerToJson(nlohmann::json &pj,const ZT_Peer *peer) +{ + char tmp[256]; + + const char *prole = ""; + switch(peer->role) { + case ZT_PEER_ROLE_LEAF: prole = "LEAF"; break; + case ZT_PEER_ROLE_MOON: prole = "MOON"; break; + case ZT_PEER_ROLE_PLANET: prole = "PLANET"; break; + } + + Utils::snprintf(tmp,sizeof(tmp),"%.10llx",peer->address); + pj["address"] = tmp; + pj["versionMajor"] = peer->versionMajor; + pj["versionMinor"] = peer->versionMinor; + pj["versionRev"] = peer->versionRev; + Utils::snprintf(tmp,sizeof(tmp),"%d.%d.%d",peer->versionMajor,peer->versionMinor,peer->versionRev); + pj["version"] = tmp; + pj["latency"] = peer->latency; + pj["role"] = prole; + + nlohmann::json pa = nlohmann::json::array(); + for(unsigned int i=0;ipathCount;++i) { + nlohmann::json j; + j["address"] = reinterpret_cast(&(peer->paths[i].address))->toString(); + j["lastSend"] = peer->paths[i].lastSend; + j["lastReceive"] = peer->paths[i].lastReceive; + j["trustedPathId"] = peer->paths[i].trustedPathId; + j["linkQuality"] = (double)peer->paths[i].linkQuality / (double)ZT_PATH_LINK_QUALITY_MAX; + j["active"] = (bool)(peer->paths[i].expired == 0); + j["expired"] = (bool)(peer->paths[i].expired != 0); + j["preferred"] = (bool)(peer->paths[i].preferred != 0); + pa.push_back(j); + } + pj["paths"] = pa; +} + +static void _moonToJson(nlohmann::json &mj,const World &world) +{ + char tmp[64]; + Utils::snprintf(tmp,sizeof(tmp),"%.16llx",world.id()); + mj["id"] = tmp; + mj["timestamp"] = world.timestamp(); + mj["signature"] = Utils::hex(world.signature().data,(unsigned int)world.signature().size()); + mj["updatesMustBeSignedBy"] = Utils::hex(world.updatesMustBeSignedBy().data,(unsigned int)world.updatesMustBeSignedBy().size()); + nlohmann::json ra = nlohmann::json::array(); + for(std::vector::const_iterator r(world.roots().begin());r!=world.roots().end();++r) { + nlohmann::json rj; + rj["identity"] = r->identity.toString(false); + nlohmann::json eps = nlohmann::json::array(); + for(std::vector::const_iterator a(r->stableEndpoints.begin());a!=r->stableEndpoints.end();++a) + eps.push_back(a->toString()); + rj["stableEndpoints"] = eps; + ra.push_back(rj); + } + mj["roots"] = ra; + mj["waiting"] = false; +} + +class OneServiceImpl; + +static int SnodeVirtualNetworkConfigFunction(ZT_Node *node,void *uptr,void *tptr,uint64_t nwid,void **nuptr,enum ZT_VirtualNetworkConfigOperation op,const ZT_VirtualNetworkConfig *nwconf); +static void SnodeEventCallback(ZT_Node *node,void *uptr,void *tptr,enum ZT_Event event,const void *metaData); +static long SnodeDataStoreGetFunction(ZT_Node *node,void *uptr,void *tptr,const char *name,void *buf,unsigned long bufSize,unsigned long readIndex,unsigned long *totalSize); +static int SnodeDataStorePutFunction(ZT_Node *node,void *uptr,void *tptr,const char *name,const void *data,unsigned long len,int secure); +static int SnodeWirePacketSendFunction(ZT_Node *node,void *uptr,void *tptr,const struct sockaddr_storage *localAddr,const struct sockaddr_storage *addr,const void *data,unsigned int len,unsigned int ttl); +static void SnodeVirtualNetworkFrameFunction(ZT_Node *node,void *uptr,void *tptr,uint64_t nwid,void **nuptr,uint64_t sourceMac,uint64_t destMac,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len); +static int SnodePathCheckFunction(ZT_Node *node,void *uptr,void *tptr,uint64_t ztaddr,const struct sockaddr_storage *localAddr,const struct sockaddr_storage *remoteAddr); +static int SnodePathLookupFunction(ZT_Node *node,void *uptr,void *tptr,uint64_t ztaddr,int family,struct sockaddr_storage *result); + +#ifdef ZT_ENABLE_CLUSTER +static void SclusterSendFunction(void *uptr,unsigned int toMemberId,const void *data,unsigned int len); +static int SclusterGeoIpFunction(void *uptr,const struct sockaddr_storage *addr,int *x,int *y,int *z); +#endif + +static void StapFrameHandler(void *uptr,void *tptr,uint64_t nwid,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len); + +static int ShttpOnMessageBegin(http_parser *parser); +static int ShttpOnUrl(http_parser *parser,const char *ptr,size_t length); +#if (HTTP_PARSER_VERSION_MAJOR >= 2) && (HTTP_PARSER_VERSION_MINOR >= 2) +static int ShttpOnStatus(http_parser *parser,const char *ptr,size_t length); +#else +static int ShttpOnStatus(http_parser *parser); +#endif +static int ShttpOnHeaderField(http_parser *parser,const char *ptr,size_t length); +static int ShttpOnValue(http_parser *parser,const char *ptr,size_t length); +static int ShttpOnHeadersComplete(http_parser *parser); +static int ShttpOnBody(http_parser *parser,const char *ptr,size_t length); +static int ShttpOnMessageComplete(http_parser *parser); + +#if (HTTP_PARSER_VERSION_MAJOR >= 2) && (HTTP_PARSER_VERSION_MINOR >= 1) +static const struct http_parser_settings HTTP_PARSER_SETTINGS = { + ShttpOnMessageBegin, + ShttpOnUrl, + ShttpOnStatus, + ShttpOnHeaderField, + ShttpOnValue, + ShttpOnHeadersComplete, + ShttpOnBody, + ShttpOnMessageComplete +}; +#else +static const struct http_parser_settings HTTP_PARSER_SETTINGS = { + ShttpOnMessageBegin, + ShttpOnUrl, + ShttpOnHeaderField, + ShttpOnValue, + ShttpOnHeadersComplete, + ShttpOnBody, + ShttpOnMessageComplete +}; +#endif + +struct TcpConnection +{ + enum { + TCP_HTTP_INCOMING, + TCP_HTTP_OUTGOING, // not currently used + TCP_TUNNEL_OUTGOING // fale-SSL outgoing tunnel -- HTTP-related fields are not used + } type; + + bool shouldKeepAlive; + OneServiceImpl *parent; + PhySocket *sock; + InetAddress from; + http_parser parser; + unsigned long messageSize; + uint64_t lastActivity; + + std::string currentHeaderField; + std::string currentHeaderValue; + + std::string url; + std::string status; + std::map< std::string,std::string > headers; + std::string body; + + std::string writeBuf; + Mutex writeBuf_m; +}; + +// Used to pseudo-randomize local source port picking +static volatile unsigned int _udpPortPickerCounter = 0; + +class OneServiceImpl : public OneService +{ +public: + // begin member variables -------------------------------------------------- + + const std::string _homePath; + std::string _authToken; + std::string _controllerDbPath; + EmbeddedNetworkController *_controller; + Phy _phy; + Node *_node; + SoftwareUpdater *_updater; + bool _updateAutoApply; + unsigned int _primaryPort; + + // Local configuration and memo-ized static path definitions + json _localConfig; + Hashtable< uint64_t,std::vector > _v4Hints; + Hashtable< uint64_t,std::vector > _v6Hints; + Hashtable< uint64_t,std::vector > _v4Blacklists; + Hashtable< uint64_t,std::vector > _v6Blacklists; + std::vector< InetAddress > _globalV4Blacklist; + std::vector< InetAddress > _globalV6Blacklist; + std::vector< InetAddress > _allowManagementFrom; + std::vector< std::string > _interfacePrefixBlacklist; + Mutex _localConfig_m; + + /* + * To attempt to handle NAT/gateway craziness we use three local UDP ports: + * + * [0] is the normal/default port, usually 9993 + * [1] is a port dervied from our ZeroTier address + * [2] is a port computed from the normal/default for use with uPnP/NAT-PMP mappings + * + * [2] exists because on some gateways trying to do regular NAT-t interferes + * destructively with uPnP port mapping behavior in very weird buggy ways. + * It's only used if uPnP/NAT-PMP is enabled in this build. + */ + Binder _bindings[3]; + unsigned int _ports[3]; + uint16_t _portsBE[3]; // ports in big-endian network byte order as in sockaddr + + // Sockets for JSON API -- bound only to V4 and V6 localhost + PhySocket *_v4TcpControlSocket; + PhySocket *_v6TcpControlSocket; + + // Time we last received a packet from a global address + uint64_t _lastDirectReceiveFromGlobal; +#ifdef ZT_TCP_FALLBACK_RELAY + uint64_t _lastSendToGlobalV4; +#endif + + // Last potential sleep/wake event + uint64_t _lastRestart; + + // Deadline for the next background task service function + volatile uint64_t _nextBackgroundTaskDeadline; + + // Configured networks + struct NetworkState + { + NetworkState() : + tap((EthernetTap *)0) + { + // Real defaults are in network 'up' code in network event handler + settings.allowManaged = true; + settings.allowGlobal = false; + settings.allowDefault = false; + } + + EthernetTap *tap; + ZT_VirtualNetworkConfig config; // memcpy() of raw config from core + std::vector managedIps; + std::list< SharedPtr > managedRoutes; + NetworkSettings settings; + }; + std::map _nets; + Mutex _nets_m; + + // Active TCP/IP connections + std::set< TcpConnection * > _tcpConnections; // no mutex for this since it's done in the main loop thread only + TcpConnection *_tcpFallbackTunnel; + + // Termination status information + ReasonForTermination _termReason; + std::string _fatalErrorMessage; + Mutex _termReason_m; + + // uPnP/NAT-PMP port mapper if enabled + bool _portMappingEnabled; // local.conf settings +#ifdef ZT_USE_MINIUPNPC + PortMapper *_portMapper; +#endif + + // Cluster management instance if enabled +#ifdef ZT_ENABLE_CLUSTER + PhySocket *_clusterMessageSocket; + ClusterDefinition *_clusterDefinition; + unsigned int _clusterMemberId; +#endif + + // Set to false to force service to stop + volatile bool _run; + Mutex _run_m; + + // end member variables ---------------------------------------------------- + + OneServiceImpl(const char *hp,unsigned int port) : + _homePath((hp) ? hp : ".") + ,_controllerDbPath(_homePath + ZT_PATH_SEPARATOR_S ZT_CONTROLLER_DB_PATH) + ,_controller((EmbeddedNetworkController *)0) + ,_phy(this,false,true) + ,_node((Node *)0) + ,_updater((SoftwareUpdater *)0) + ,_updateAutoApply(false) + ,_primaryPort(port) + ,_v4TcpControlSocket((PhySocket *)0) + ,_v6TcpControlSocket((PhySocket *)0) + ,_lastDirectReceiveFromGlobal(0) +#ifdef ZT_TCP_FALLBACK_RELAY + ,_lastSendToGlobalV4(0) +#endif + ,_lastRestart(0) + ,_nextBackgroundTaskDeadline(0) + ,_tcpFallbackTunnel((TcpConnection *)0) + ,_termReason(ONE_STILL_RUNNING) + ,_portMappingEnabled(true) +#ifdef ZT_USE_MINIUPNPC + ,_portMapper((PortMapper *)0) +#endif +#ifdef ZT_ENABLE_CLUSTER + ,_clusterMessageSocket((PhySocket *)0) + ,_clusterDefinition((ClusterDefinition *)0) + ,_clusterMemberId(0) +#endif + ,_run(true) + { + _ports[0] = 0; + _ports[1] = 0; + _ports[2] = 0; + } + + virtual ~OneServiceImpl() + { + for(int i=0;i<3;++i) + _bindings[i].closeAll(_phy); + + _phy.close(_v4TcpControlSocket); + _phy.close(_v6TcpControlSocket); + +#ifdef ZT_ENABLE_CLUSTER + _phy.close(_clusterMessageSocket); +#endif + +#ifdef ZT_USE_MINIUPNPC + delete _portMapper; +#endif + delete _controller; +#ifdef ZT_ENABLE_CLUSTER + delete _clusterDefinition; +#endif + } + + virtual ReasonForTermination run() + { + try { + { + const std::string authTokenPath(_homePath + ZT_PATH_SEPARATOR_S "authtoken.secret"); + if (!OSUtils::readFile(authTokenPath.c_str(),_authToken)) { + unsigned char foo[24]; + Utils::getSecureRandom(foo,sizeof(foo)); + _authToken = ""; + for(unsigned int i=0;i 0) ) { + trustedPathIds[trustedPathCount] = trustedPathId; + trustedPathNetworks[trustedPathCount] = trustedPathNetwork; + ++trustedPathCount; + } + } + fclose(trustpaths); + } + + // Read local config file + Mutex::Lock _l2(_localConfig_m); + std::string lcbuf; + if (OSUtils::readFile((_homePath + ZT_PATH_SEPARATOR_S "local.conf").c_str(),lcbuf)) { + try { + _localConfig = OSUtils::jsonParse(lcbuf); + if (!_localConfig.is_object()) { + fprintf(stderr,"WARNING: unable to parse local.conf (root element is not a JSON object)" ZT_EOL_S); + } + } catch ( ... ) { + fprintf(stderr,"WARNING: unable to parse local.conf (invalid JSON)" ZT_EOL_S); + } + } + + // Get any trusted paths in local.conf (we'll parse the rest of physical[] elsewhere) + json &physical = _localConfig["physical"]; + if (physical.is_object()) { + for(json::iterator phy(physical.begin());phy!=physical.end();++phy) { + InetAddress net(OSUtils::jsonString(phy.key(),"")); + if (net) { + if (phy.value().is_object()) { + uint64_t tpid; + if ((tpid = OSUtils::jsonInt(phy.value()["trustedPathId"],0ULL)) != 0ULL) { + if ( ((net.ss_family == AF_INET)||(net.ss_family == AF_INET6)) && (trustedPathCount < ZT_MAX_TRUSTED_PATHS) && (net.ipScope() != InetAddress::IP_SCOPE_GLOBAL) && (net.netmaskBits() > 0) ) { + trustedPathIds[trustedPathCount] = tpid; + trustedPathNetworks[trustedPathCount] = net; + ++trustedPathCount; + } + } + } + } + } + } + + // Set trusted paths if there are any + if (trustedPathCount) + _node->setTrustedPaths(reinterpret_cast(trustedPathNetworks),trustedPathIds,trustedPathCount); + } + applyLocalConfig(); + + // Bind TCP control socket + const int portTrials = (_primaryPort == 0) ? 256 : 1; // if port is 0, pick random + for(int k=0;k 0) ? 0 : 0x7f000001)); // right now we just listen for TCP @127.0.0.1 + in4.sin_port = Utils::hton((uint16_t)_primaryPort); + _v4TcpControlSocket = _phy.tcpListen((const struct sockaddr *)&in4,this); + + struct sockaddr_in6 in6; + memset((void *)&in6,0,sizeof(in6)); + in6.sin6_family = AF_INET6; + in6.sin6_port = in4.sin_port; + if (_allowManagementFrom.size() == 0) + in6.sin6_addr.s6_addr[15] = 1; // IPv6 localhost == ::1 + _v6TcpControlSocket = _phy.tcpListen((const struct sockaddr *)&in6,this); + + // We must bind one of IPv4 or IPv6 -- support either failing to support hosts that + // have only IPv4 or only IPv6 stacks. + if ((_v4TcpControlSocket)||(_v6TcpControlSocket)) { + _ports[0] = _primaryPort; + break; + } else { + if (_v4TcpControlSocket) + _phy.close(_v4TcpControlSocket,false); + if (_v6TcpControlSocket) + _phy.close(_v6TcpControlSocket,false); + _primaryPort = 0; + } + } else { + _primaryPort = 0; + } + } + if (_ports[0] == 0) { + Mutex::Lock _l(_termReason_m); + _termReason = ONE_UNRECOVERABLE_ERROR; + _fatalErrorMessage = "cannot bind to local control interface port"; + return _termReason; + } + + // Write file containing primary port to be read by CLIs, etc. + char portstr[64]; + Utils::snprintf(portstr,sizeof(portstr),"%u",_ports[0]); + OSUtils::writeFile((_homePath + ZT_PATH_SEPARATOR_S "zerotier-one.port").c_str(),std::string(portstr)); + + // Attempt to bind to a secondary port chosen from our ZeroTier address. + // This exists because there are buggy NATs out there that fail if more + // than one device behind the same NAT tries to use the same internal + // private address port number. + _ports[1] = 20000 + ((unsigned int)_node->address() % 45500); + for(int i=0;;++i) { + if (i > 1000) { + _ports[1] = 0; + break; + } else if (++_ports[1] >= 65536) { + _ports[1] = 20000; + } + if (_trialBind(_ports[1])) + break; + } + +#ifdef ZT_USE_MINIUPNPC + if (_portMappingEnabled) { + // If we're running uPnP/NAT-PMP, bind a *third* port for that. We can't + // use the other two ports for that because some NATs do really funky + // stuff with ports that are explicitly mapped that breaks things. + if (_ports[1]) { + _ports[2] = _ports[1]; + for(int i=0;;++i) { + if (i > 1000) { + _ports[2] = 0; + break; + } else if (++_ports[2] >= 65536) { + _ports[2] = 20000; + } + if (_trialBind(_ports[2])) + break; + } + if (_ports[2]) { + char uniqueName[64]; + Utils::snprintf(uniqueName,sizeof(uniqueName),"ZeroTier/%.10llx@%u",_node->address(),_ports[2]); + _portMapper = new PortMapper(_ports[2],uniqueName); + } + } + } +#endif + + // Populate ports in big-endian format for quick compare + for(int i=0;i<3;++i) + _portsBE[i] = Utils::hton((uint16_t)_ports[i]); + + _controller = new EmbeddedNetworkController(_node,_controllerDbPath.c_str()); + _node->setNetconfMaster((void *)_controller); + +#ifdef ZT_ENABLE_CLUSTER + if (OSUtils::fileExists((_homePath + ZT_PATH_SEPARATOR_S "cluster").c_str())) { + _clusterDefinition = new ClusterDefinition(_node->address(),(_homePath + ZT_PATH_SEPARATOR_S "cluster").c_str()); + if (_clusterDefinition->size() > 0) { + std::vector members(_clusterDefinition->members()); + for(std::vector::iterator m(members.begin());m!=members.end();++m) { + PhySocket *cs = _phy.udpBind(reinterpret_cast(&(m->clusterEndpoint))); + if (cs) { + if (_clusterMessageSocket) { + _phy.close(_clusterMessageSocket,false); + _phy.close(cs,false); + + Mutex::Lock _l(_termReason_m); + _termReason = ONE_UNRECOVERABLE_ERROR; + _fatalErrorMessage = "cluster: can't determine my cluster member ID: able to bind more than one cluster message socket IP/port!"; + return _termReason; + } + _clusterMessageSocket = cs; + _clusterMemberId = m->id; + } + } + + if (!_clusterMessageSocket) { + Mutex::Lock _l(_termReason_m); + _termReason = ONE_UNRECOVERABLE_ERROR; + _fatalErrorMessage = "cluster: can't determine my cluster member ID: unable to bind to any cluster message socket IP/port."; + return _termReason; + } + + const ClusterDefinition::MemberDefinition &me = (*_clusterDefinition)[_clusterMemberId]; + InetAddress endpoints[255]; + unsigned int numEndpoints = 0; + for(std::vector::const_iterator i(me.zeroTierEndpoints.begin());i!=me.zeroTierEndpoints.end();++i) + endpoints[numEndpoints++] = *i; + + if (_node->clusterInit(_clusterMemberId,reinterpret_cast(endpoints),numEndpoints,me.x,me.y,me.z,&SclusterSendFunction,this,_clusterDefinition->geo().available() ? &SclusterGeoIpFunction : 0,this) == ZT_RESULT_OK) { + std::vector members(_clusterDefinition->members()); + for(std::vector::iterator m(members.begin());m!=members.end();++m) { + if (m->id != _clusterMemberId) + _node->clusterAddMember(m->id); + } + } + } else { + delete _clusterDefinition; + _clusterDefinition = (ClusterDefinition *)0; + } + } +#endif + + { // Load existing networks + std::vector networksDotD(OSUtils::listDirectory((_homePath + ZT_PATH_SEPARATOR_S "networks.d").c_str())); + for(std::vector::iterator f(networksDotD.begin());f!=networksDotD.end();++f) { + std::size_t dot = f->find_last_of('.'); + if ((dot == 16)&&(f->substr(16) == ".conf")) + _node->join(Utils::hexStrToU64(f->substr(0,dot).c_str()),(void *)0,(void *)0); + } + } + { // Load existing moons + std::vector moonsDotD(OSUtils::listDirectory((_homePath + ZT_PATH_SEPARATOR_S "moons.d").c_str())); + for(std::vector::iterator f(moonsDotD.begin());f!=moonsDotD.end();++f) { + std::size_t dot = f->find_last_of('.'); + if ((dot == 16)&&(f->substr(16) == ".moon")) + _node->orbit((void *)0,Utils::hexStrToU64(f->substr(0,dot).c_str()),0); + } + } + + _nextBackgroundTaskDeadline = 0; + uint64_t clockShouldBe = OSUtils::now(); + _lastRestart = clockShouldBe; + uint64_t lastTapMulticastGroupCheck = 0; + uint64_t lastBindRefresh = 0; + uint64_t lastUpdateCheck = clockShouldBe; + uint64_t lastLocalInterfaceAddressCheck = (clockShouldBe - ZT_LOCAL_INTERFACE_CHECK_INTERVAL) + 15000; // do this in 15s to give portmapper time to configure and other things time to settle + uint64_t lastCleanedIddb = 0; + for(;;) { + _run_m.lock(); + if (!_run) { + _run_m.unlock(); + _termReason_m.lock(); + _termReason = ONE_NORMAL_TERMINATION; + _termReason_m.unlock(); + break; + } else { + _run_m.unlock(); + } + + const uint64_t now = OSUtils::now(); + + // Clean iddb.d on start and every 24 hours + if ((now - lastCleanedIddb) > 86400000) { + lastCleanedIddb = now; + OSUtils::cleanDirectory((_homePath + ZT_PATH_SEPARATOR_S "iddb.d").c_str(),now - ZT_IDDB_CLEANUP_AGE); + } + + // Attempt to detect sleep/wake events by detecting delay overruns + bool restarted = false; + if ((now > clockShouldBe)&&((now - clockShouldBe) > 10000)) { + _lastRestart = now; + restarted = true; + } + + // Check for updates (if enabled) + if ((_updater)&&((now - lastUpdateCheck) > 10000)) { + lastUpdateCheck = now; + if (_updater->check(now) && _updateAutoApply) + _updater->apply(); + } + + // Refresh bindings in case device's interfaces have changed, and also sync routes to update any shadow routes (e.g. shadow default) + if (((now - lastBindRefresh) >= ZT_BINDER_REFRESH_PERIOD)||(restarted)) { + lastBindRefresh = now; + for(int i=0;i<3;++i) { + if (_ports[i]) { + _bindings[i].refresh(_phy,_ports[i],*this); + } + } + { + Mutex::Lock _l(_nets_m); + for(std::map::iterator n(_nets.begin());n!=_nets.end();++n) { + if (n->second.tap) + syncManagedStuff(n->second,false,true); + } + } + } + + uint64_t dl = _nextBackgroundTaskDeadline; + if (dl <= now) { + _node->processBackgroundTasks((void *)0,now,&_nextBackgroundTaskDeadline); + dl = _nextBackgroundTaskDeadline; + } + + if ((_tcpFallbackTunnel)&&((now - _lastDirectReceiveFromGlobal) < (ZT_TCP_FALLBACK_AFTER / 2))) + _phy.close(_tcpFallbackTunnel->sock); + + if ((now - lastTapMulticastGroupCheck) >= ZT_TAP_CHECK_MULTICAST_INTERVAL) { + lastTapMulticastGroupCheck = now; + Mutex::Lock _l(_nets_m); + for(std::map::const_iterator n(_nets.begin());n!=_nets.end();++n) { + if (n->second.tap) { + std::vector added,removed; + n->second.tap->scanMulticastGroups(added,removed); + for(std::vector::iterator m(added.begin());m!=added.end();++m) + _node->multicastSubscribe((void *)0,n->first,m->mac().toInt(),m->adi()); + for(std::vector::iterator m(removed.begin());m!=removed.end();++m) + _node->multicastUnsubscribe(n->first,m->mac().toInt(),m->adi()); + } + } + } + + if ((now - lastLocalInterfaceAddressCheck) >= ZT_LOCAL_INTERFACE_CHECK_INTERVAL) { + lastLocalInterfaceAddressCheck = now; + + _node->clearLocalInterfaceAddresses(); + +#ifdef ZT_USE_MINIUPNPC + if (_portMapper) { + std::vector mappedAddresses(_portMapper->get()); + for(std::vector::const_iterator ext(mappedAddresses.begin());ext!=mappedAddresses.end();++ext) + _node->addLocalInterfaceAddress(reinterpret_cast(&(*ext))); + } +#endif + + std::vector boundAddrs(_bindings[0].allBoundLocalInterfaceAddresses()); + for(std::vector::const_iterator i(boundAddrs.begin());i!=boundAddrs.end();++i) + _node->addLocalInterfaceAddress(reinterpret_cast(&(*i))); + } + + const unsigned long delay = (dl > now) ? (unsigned long)(dl - now) : 100; + clockShouldBe = now + (uint64_t)delay; + _phy.poll(delay); + } + } catch (std::exception &exc) { + Mutex::Lock _l(_termReason_m); + _termReason = ONE_UNRECOVERABLE_ERROR; + _fatalErrorMessage = exc.what(); + } catch ( ... ) { + Mutex::Lock _l(_termReason_m); + _termReason = ONE_UNRECOVERABLE_ERROR; + _fatalErrorMessage = "unexpected exception in main thread"; + } + + try { + while (!_tcpConnections.empty()) + _phy.close((*_tcpConnections.begin())->sock); + } catch ( ... ) {} + + { + Mutex::Lock _l(_nets_m); + for(std::map::iterator n(_nets.begin());n!=_nets.end();++n) + delete n->second.tap; + _nets.clear(); + } + + delete _updater; + _updater = (SoftwareUpdater *)0; + delete _node; + _node = (Node *)0; + + return _termReason; + } + + virtual ReasonForTermination reasonForTermination() const + { + Mutex::Lock _l(_termReason_m); + return _termReason; + } + + virtual std::string fatalErrorMessage() const + { + Mutex::Lock _l(_termReason_m); + return _fatalErrorMessage; + } + + virtual std::string portDeviceName(uint64_t nwid) const + { + Mutex::Lock _l(_nets_m); + std::map::const_iterator n(_nets.find(nwid)); + if ((n != _nets.end())&&(n->second.tap)) + return n->second.tap->deviceName(); + else return std::string(); + } + + virtual void terminate() + { + _run_m.lock(); + _run = false; + _run_m.unlock(); + _phy.whack(); + } + + virtual bool getNetworkSettings(const uint64_t nwid,NetworkSettings &settings) const + { + Mutex::Lock _l(_nets_m); + std::map::const_iterator n(_nets.find(nwid)); + if (n == _nets.end()) + return false; + settings = n->second.settings; + return true; + } + + virtual bool setNetworkSettings(const uint64_t nwid,const NetworkSettings &settings) + { + Mutex::Lock _l(_nets_m); + + std::map::iterator n(_nets.find(nwid)); + if (n == _nets.end()) + return false; + n->second.settings = settings; + + char nlcpath[256]; + Utils::snprintf(nlcpath,sizeof(nlcpath),"%s" ZT_PATH_SEPARATOR_S "networks.d" ZT_PATH_SEPARATOR_S "%.16llx.local.conf",_homePath.c_str(),nwid); + FILE *out = fopen(nlcpath,"w"); + if (out) { + fprintf(out,"allowManaged=%d\n",(int)n->second.settings.allowManaged); + fprintf(out,"allowGlobal=%d\n",(int)n->second.settings.allowGlobal); + fprintf(out,"allowDefault=%d\n",(int)n->second.settings.allowDefault); + fclose(out); + } + + if (n->second.tap) + syncManagedStuff(n->second,true,true); + + return true; + } + + // Internal implementation methods ----------------------------------------- + + inline unsigned int handleControlPlaneHttpRequest( + const InetAddress &fromAddress, + unsigned int httpMethod, + const std::string &path, + const std::map &headers, + const std::string &body, + std::string &responseBody, + std::string &responseContentType) + { + char tmp[256]; + unsigned int scode = 404; + json res; + std::vector ps(OSUtils::split(path.c_str(),"/","","")); + std::map urlArgs; + + /* Note: this is kind of restricted in what it'll take. It does not support + * URL encoding, and /'s in URL args will screw it up. But the only URL args + * it really uses in ?jsonp=funcionName, and otherwise it just takes simple + * paths to simply-named resources. */ + if (ps.size() > 0) { + std::size_t qpos = ps[ps.size() - 1].find('?'); + if (qpos != std::string::npos) { + std::string args(ps[ps.size() - 1].substr(qpos + 1)); + ps[ps.size() - 1] = ps[ps.size() - 1].substr(0,qpos); + std::vector asplit(OSUtils::split(args.c_str(),"&","","")); + for(std::vector::iterator a(asplit.begin());a!=asplit.end();++a) { + std::size_t eqpos = a->find('='); + if (eqpos == std::string::npos) + urlArgs[*a] = ""; + else urlArgs[a->substr(0,eqpos)] = a->substr(eqpos + 1); + } + } + } + + bool isAuth = false; + { + std::map::const_iterator ah(headers.find("x-zt1-auth")); + if ((ah != headers.end())&&(_authToken == ah->second)) { + isAuth = true; + } else { + ah = urlArgs.find("auth"); + if ((ah != urlArgs.end())&&(_authToken == ah->second)) + isAuth = true; + } + } + +#ifdef __SYNOLOGY__ + // Authenticate via Synology's built-in cgi script + if (!isAuth) { + /* + fprintf(stderr, "path = %s\n", path.c_str()); + fprintf(stderr, "headers.size=%d\n", headers.size()); + std::map::const_iterator it(headers.begin()); + while(it != headers.end()) { + fprintf(stderr,"header[%s] = %s\n", (it->first).c_str(), (it->second).c_str()); + it++; + } + */ + // parse out url args + int synotoken_pos = path.find("SynoToken"); + int argpos = path.find("?"); + if(synotoken_pos != std::string::npos && argpos != std::string::npos) { + std::string cookie = path.substr(argpos+1, synotoken_pos-(argpos+1)); + std::string synotoken = path.substr(synotoken_pos); + std::string cookie_val = cookie.substr(cookie.find("=")+1); + std::string synotoken_val = synotoken.substr(synotoken.find("=")+1); + // Set necessary env for auth script + std::map::const_iterator ah2(headers.find("x-forwarded-for")); + setenv("HTTP_COOKIE", cookie_val.c_str(), true); + setenv("HTTP_X_SYNO_TOKEN", synotoken_val.c_str(), true); + setenv("REMOTE_ADDR", ah2->second.c_str(),true); + //fprintf(stderr, "HTTP_COOKIE: %s\n",std::getenv ("HTTP_COOKIE")); + //fprintf(stderr, "HTTP_X_SYNO_TOKEN: %s\n",std::getenv ("HTTP_X_SYNO_TOKEN")); + //fprintf(stderr, "REMOTE_ADDR: %s\n",std::getenv ("REMOTE_ADDR")); + // check synology web auth + char user[256], buf[1024]; + FILE *fp = NULL; + bzero(user, 256); + fp = popen("/usr/syno/synoman/webman/modules/authenticate.cgi", "r"); + if(!fp) + isAuth = false; + else { + bzero(buf, sizeof(buf)); + fread(buf, 1024, 1, fp); + if(strlen(buf) > 0) { + snprintf(user, 256, "%s", buf); + isAuth = true; + } + } + pclose(fp); + } + } +#endif + + if (httpMethod == HTTP_GET) { + if (isAuth) { + if (ps[0] == "status") { + ZT_NodeStatus status; + _node->status(&status); + + Utils::snprintf(tmp,sizeof(tmp),"%.10llx",status.address); + res["address"] = tmp; + res["publicIdentity"] = status.publicIdentity; + res["online"] = (bool)(status.online != 0); + res["tcpFallbackActive"] = (_tcpFallbackTunnel != (TcpConnection *)0); + res["versionMajor"] = ZEROTIER_ONE_VERSION_MAJOR; + res["versionMinor"] = ZEROTIER_ONE_VERSION_MINOR; + res["versionRev"] = ZEROTIER_ONE_VERSION_REVISION; + res["versionBuild"] = ZEROTIER_ONE_VERSION_BUILD; + Utils::snprintf(tmp,sizeof(tmp),"%d.%d.%d",ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION); + res["version"] = tmp; + res["clock"] = OSUtils::now(); + + { + Mutex::Lock _l(_localConfig_m); + res["config"] = _localConfig; + } + json &settings = res["config"]["settings"]; + settings["primaryPort"] = OSUtils::jsonInt(settings["primaryPort"],(uint64_t)_primaryPort) & 0xffff; +#ifdef ZT_USE_MINIUPNPC + settings["portMappingEnabled"] = OSUtils::jsonBool(settings["portMappingEnabled"],true); +#else + settings["portMappingEnabled"] = false; // not supported in build +#endif + settings["softwareUpdate"] = OSUtils::jsonString(settings["softwareUpdate"],ZT_SOFTWARE_UPDATE_DEFAULT); + settings["softwareUpdateChannel"] = OSUtils::jsonString(settings["softwareUpdateChannel"],ZT_SOFTWARE_UPDATE_DEFAULT_CHANNEL); + + const World planet(_node->planet()); + res["planetWorldId"] = planet.id(); + res["planetWorldTimestamp"] = planet.timestamp(); + +#ifdef ZT_ENABLE_CLUSTER + json cj; + ZT_ClusterStatus cs; + _node->clusterStatus(&cs); + if (cs.clusterSize >= 1) { + json cja = json::array(); + for(unsigned int i=0;i moons(_node->moons()); + if (ps.size() == 1) { + // Return [array] of all moons + + res = json::array(); + for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { + json mj; + _moonToJson(mj,*m); + res.push_back(mj); + } + + scode = 200; + } else { + // Return a single moon by ID + + const uint64_t id = Utils::hexStrToU64(ps[1].c_str()); + for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { + if (m->id() == id) { + _moonToJson(res,*m); + scode = 200; + break; + } + } + + } + } else if (ps[0] == "network") { + ZT_VirtualNetworkList *nws = _node->networks(); + if (nws) { + if (ps.size() == 1) { + // Return [array] of all networks + + res = nlohmann::json::array(); + for(unsigned long i=0;inetworkCount;++i) { + OneService::NetworkSettings localSettings; + getNetworkSettings(nws->networks[i].nwid,localSettings); + nlohmann::json nj; + _networkToJson(nj,&(nws->networks[i]),portDeviceName(nws->networks[i].nwid),localSettings); + res.push_back(nj); + } + + scode = 200; + } else if (ps.size() == 2) { + // Return a single network by ID or 404 if not found + + const uint64_t wantnw = Utils::hexStrToU64(ps[1].c_str()); + for(unsigned long i=0;inetworkCount;++i) { + if (nws->networks[i].nwid == wantnw) { + OneService::NetworkSettings localSettings; + getNetworkSettings(nws->networks[i].nwid,localSettings); + _networkToJson(res,&(nws->networks[i]),portDeviceName(nws->networks[i].nwid),localSettings); + scode = 200; + break; + } + } + + } else scode = 404; + _node->freeQueryResult((void *)nws); + } else scode = 500; + } else if (ps[0] == "peer") { + ZT_PeerList *pl = _node->peers(); + if (pl) { + if (ps.size() == 1) { + // Return [array] of all peers + + res = nlohmann::json::array(); + for(unsigned long i=0;ipeerCount;++i) { + nlohmann::json pj; + _peerToJson(pj,&(pl->peers[i])); + res.push_back(pj); + } + + scode = 200; + } else if (ps.size() == 2) { + // Return a single peer by ID or 404 if not found + + uint64_t wantp = Utils::hexStrToU64(ps[1].c_str()); + for(unsigned long i=0;ipeerCount;++i) { + if (pl->peers[i].address == wantp) { + _peerToJson(res,&(pl->peers[i])); + scode = 200; + break; + } + } + + } else scode = 404; + _node->freeQueryResult((void *)pl); + } else scode = 500; + } else { + if (_controller) { + scode = _controller->handleControlPlaneHttpGET(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); + } else scode = 404; + } + + } else scode = 401; // isAuth == false + } else if ((httpMethod == HTTP_POST)||(httpMethod == HTTP_PUT)) { + if (isAuth) { + + if (ps[0] == "moon") { + if (ps.size() == 2) { + + uint64_t seed = 0; + try { + json j(OSUtils::jsonParse(body)); + if (j.is_object()) { + seed = Utils::hexStrToU64(OSUtils::jsonString(j["seed"],"0").c_str()); + } + } catch ( ... ) { + // discard invalid JSON + } + + std::vector moons(_node->moons()); + const uint64_t id = Utils::hexStrToU64(ps[1].c_str()); + for(std::vector::const_iterator m(moons.begin());m!=moons.end();++m) { + if (m->id() == id) { + _moonToJson(res,*m); + scode = 200; + break; + } + } + + if ((scode != 200)&&(seed != 0)) { + char tmp[64]; + Utils::snprintf(tmp,sizeof(tmp),"%.16llx",id); + res["id"] = tmp; + res["roots"] = json::array(); + res["timestamp"] = 0; + res["signature"] = json(); + res["updatesMustBeSignedBy"] = json(); + res["waiting"] = true; + _node->orbit((void *)0,id,seed); + scode = 200; + } + + } else scode = 404; + } else if (ps[0] == "network") { + if (ps.size() == 2) { + + uint64_t wantnw = Utils::hexStrToU64(ps[1].c_str()); + _node->join(wantnw,(void *)0,(void *)0); // does nothing if we are a member + ZT_VirtualNetworkList *nws = _node->networks(); + if (nws) { + for(unsigned long i=0;inetworkCount;++i) { + if (nws->networks[i].nwid == wantnw) { + OneService::NetworkSettings localSettings; + getNetworkSettings(nws->networks[i].nwid,localSettings); + + try { + json j(OSUtils::jsonParse(body)); + if (j.is_object()) { + json &allowManaged = j["allowManaged"]; + if (allowManaged.is_boolean()) localSettings.allowManaged = (bool)allowManaged; + json &allowGlobal = j["allowGlobal"]; + if (allowGlobal.is_boolean()) localSettings.allowGlobal = (bool)allowGlobal; + json &allowDefault = j["allowDefault"]; + if (allowDefault.is_boolean()) localSettings.allowDefault = (bool)allowDefault; + } + } catch ( ... ) { + // discard invalid JSON + } + + setNetworkSettings(nws->networks[i].nwid,localSettings); + _networkToJson(res,&(nws->networks[i]),portDeviceName(nws->networks[i].nwid),localSettings); + + scode = 200; + break; + } + } + _node->freeQueryResult((void *)nws); + } else scode = 500; + + } else scode = 404; + } else { + if (_controller) + scode = _controller->handleControlPlaneHttpPOST(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); + else scode = 404; + } + + } else scode = 401; // isAuth == false + } else if (httpMethod == HTTP_DELETE) { + if (isAuth) { + + if (ps[0] == "moon") { + if (ps.size() == 2) { + _node->deorbit((void *)0,Utils::hexStrToU64(ps[1].c_str())); + res["result"] = true; + scode = 200; + } // else 404 + } else if (ps[0] == "network") { + ZT_VirtualNetworkList *nws = _node->networks(); + if (nws) { + if (ps.size() == 2) { + uint64_t wantnw = Utils::hexStrToU64(ps[1].c_str()); + for(unsigned long i=0;inetworkCount;++i) { + if (nws->networks[i].nwid == wantnw) { + _node->leave(wantnw,(void **)0,(void *)0); + res["result"] = true; + scode = 200; + break; + } + } + } // else 404 + _node->freeQueryResult((void *)nws); + } else scode = 500; + } else { + if (_controller) + scode = _controller->handleControlPlaneHttpDELETE(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); + else scode = 404; + } + + } else scode = 401; // isAuth = false + } else { + scode = 400; + } + + if (responseBody.length() == 0) { + if ((res.is_object())||(res.is_array())) + responseBody = OSUtils::jsonDump(res); + else responseBody = "{}"; + responseContentType = "application/json"; + } + + // Wrap result in jsonp function call if the user included a jsonp= url argument. + // Also double-check isAuth since forbidding this without auth feels safer. + std::map::const_iterator jsonp(urlArgs.find("jsonp")); + if ((isAuth)&&(jsonp != urlArgs.end())&&(responseContentType == "application/json")) { + if (responseBody.length() > 0) + responseBody = jsonp->second + "(" + responseBody + ");"; + else responseBody = jsonp->second + "(null);"; + responseContentType = "application/javascript"; + } + + return scode; + } + + // Must be called after _localConfig is read or modified + void applyLocalConfig() + { + Mutex::Lock _l(_localConfig_m); + json lc(_localConfig); + + _v4Hints.clear(); + _v6Hints.clear(); + _v4Blacklists.clear(); + _v6Blacklists.clear(); + json &virt = lc["virtual"]; + if (virt.is_object()) { + for(json::iterator v(virt.begin());v!=virt.end();++v) { + const std::string nstr = v.key(); + if ((nstr.length() == ZT_ADDRESS_LENGTH_HEX)&&(v.value().is_object())) { + const Address ztaddr(Utils::hexStrToU64(nstr.c_str())); + if (ztaddr) { + const uint64_t ztaddr2 = ztaddr.toInt(); + std::vector &v4h = _v4Hints[ztaddr2]; + std::vector &v6h = _v6Hints[ztaddr2]; + std::vector &v4b = _v4Blacklists[ztaddr2]; + std::vector &v6b = _v6Blacklists[ztaddr2]; + + json &tryAddrs = v.value()["try"]; + if (tryAddrs.is_array()) { + for(unsigned long i=0;i 0)) { + if (phy.value().is_object()) { + if (OSUtils::jsonBool(phy.value()["blacklist"],false)) { + if (net.ss_family == AF_INET) + _globalV4Blacklist.push_back(net); + else if (net.ss_family == AF_INET6) + _globalV6Blacklist.push_back(net); + } + } + } + } + } + + _allowManagementFrom.clear(); + _interfacePrefixBlacklist.clear(); + + json &settings = lc["settings"]; + + _primaryPort = (unsigned int)OSUtils::jsonInt(settings["primaryPort"],(uint64_t)_primaryPort) & 0xffff; + _portMappingEnabled = OSUtils::jsonBool(settings["portMappingEnabled"],true); + + const std::string up(OSUtils::jsonString(settings["softwareUpdate"],ZT_SOFTWARE_UPDATE_DEFAULT)); + const bool udist = OSUtils::jsonBool(settings["softwareUpdateDist"],false); + if (((up == "apply")||(up == "download"))||(udist)) { + if (!_updater) + _updater = new SoftwareUpdater(*_node,_homePath); + _updateAutoApply = (up == "apply"); + _updater->setUpdateDistribution(udist); + _updater->setChannel(OSUtils::jsonString(settings["softwareUpdateChannel"],ZT_SOFTWARE_UPDATE_DEFAULT_CHANNEL)); + } else { + delete _updater; + _updater = (SoftwareUpdater *)0; + _updateAutoApply = false; + } + + json &ignoreIfs = settings["interfacePrefixBlacklist"]; + if (ignoreIfs.is_array()) { + for(unsigned long i=0;i 0) + _interfacePrefixBlacklist.push_back(tmp); + } + } + + json &amf = settings["allowManagementFrom"]; + if (amf.is_array()) { + for(unsigned long i=0;i 0) { + bool allowed = false; + for (InetAddress addr : n.settings.allowManagedWhitelist) { + if (addr.containsAddress(target) && addr.netmaskBits() <= target.netmaskBits()) { + allowed = true; + break; + } + } + if (!allowed) return false; + } + + if (target.isDefaultRoute()) + return n.settings.allowDefault; + switch(target.ipScope()) { + case InetAddress::IP_SCOPE_NONE: + case InetAddress::IP_SCOPE_MULTICAST: + case InetAddress::IP_SCOPE_LOOPBACK: + case InetAddress::IP_SCOPE_LINK_LOCAL: + return false; + case InetAddress::IP_SCOPE_GLOBAL: + return n.settings.allowGlobal; + default: + return true; + } + } + + // Match only an IP from a vector of IPs -- used in syncManagedStuff() + bool matchIpOnly(const std::vector &ips,const InetAddress &ip) const + { + for(std::vector::const_iterator i(ips.begin());i!=ips.end();++i) { + if (i->ipsEqual(ip)) + return true; + } + return false; + } + + // Apply or update managed IPs for a configured network (be sure n.tap exists) + void syncManagedStuff(NetworkState &n,bool syncIps,bool syncRoutes) + { + // assumes _nets_m is locked + if (syncIps) { + std::vector newManagedIps; + newManagedIps.reserve(n.config.assignedAddressCount); + for(unsigned int i=0;i(&(n.config.assignedAddresses[i])); + if (checkIfManagedIsAllowed(n,*ii)) + newManagedIps.push_back(*ii); + } + std::sort(newManagedIps.begin(),newManagedIps.end()); + newManagedIps.erase(std::unique(newManagedIps.begin(),newManagedIps.end()),newManagedIps.end()); + + for(std::vector::iterator ip(n.managedIps.begin());ip!=n.managedIps.end();++ip) { + if (std::find(newManagedIps.begin(),newManagedIps.end(),*ip) == newManagedIps.end()) { + if (!n.tap->removeIp(*ip)) + fprintf(stderr,"ERROR: unable to remove ip address %s" ZT_EOL_S, ip->toString().c_str()); + } + } +#ifdef __SYNOLOGY__ + if (!n.tap->addIpSyn(newManagedIps)) + fprintf(stderr,"ERROR: unable to add ip addresses to ifcfg" ZT_EOL_S); +#else + for(std::vector::iterator ip(newManagedIps.begin());ip!=newManagedIps.end();++ip) { + if (std::find(n.managedIps.begin(),n.managedIps.end(),*ip) == n.managedIps.end()) { + if (!n.tap->addIp(*ip)) + fprintf(stderr,"ERROR: unable to add ip address %s" ZT_EOL_S, ip->toString().c_str()); + } + } +#endif + n.managedIps.swap(newManagedIps); + } + + if (syncRoutes) { + char tapdev[64]; +#ifdef __WINDOWS__ + Utils::snprintf(tapdev,sizeof(tapdev),"%.16llx",(unsigned long long)n.tap->luid().Value); +#else + Utils::scopy(tapdev,sizeof(tapdev),n.tap->deviceName().c_str()); +#endif + + std::vector myIps(n.tap->ips()); + + // Nuke applied routes that are no longer in n.config.routes[] and/or are not allowed + for(std::list< SharedPtr >::iterator mr(n.managedRoutes.begin());mr!=n.managedRoutes.end();) { + bool haveRoute = false; + if ( (checkIfManagedIsAllowed(n,(*mr)->target())) && (((*mr)->via().ss_family != (*mr)->target().ss_family)||(!matchIpOnly(myIps,(*mr)->via()))) ) { + for(unsigned int i=0;i(&(n.config.routes[i].target)); + const InetAddress *const via = reinterpret_cast(&(n.config.routes[i].via)); + if ( ((*mr)->target() == *target) && ( ((via->ss_family == target->ss_family)&&((*mr)->via().ipsEqual(*via))) || (tapdev == (*mr)->device()) ) ) { + haveRoute = true; + break; + } + } + } + if (haveRoute) { + ++mr; + } else { + n.managedRoutes.erase(mr++); + } + } + + // Apply routes in n.config.routes[] that we haven't applied yet, and sync those we have in case shadow routes need to change + for(unsigned int i=0;i(&(n.config.routes[i].target)); + const InetAddress *const via = reinterpret_cast(&(n.config.routes[i].via)); + + if ( (!checkIfManagedIsAllowed(n,*target)) || ((via->ss_family == target->ss_family)&&(matchIpOnly(myIps,*via))) ) + continue; + + bool haveRoute = false; + + // Ignore routes implied by local managed IPs since adding the IP adds the route + for(std::vector::iterator ip(n.managedIps.begin());ip!=n.managedIps.end();++ip) { + if ((target->netmaskBits() == ip->netmaskBits())&&(target->containsAddress(*ip))) { + haveRoute = true; + break; + } + } + if (haveRoute) + continue; + + // If we've already applied this route, just sync it and continue + for(std::list< SharedPtr >::iterator mr(n.managedRoutes.begin());mr!=n.managedRoutes.end();++mr) { + if ( ((*mr)->target() == *target) && ( ((via->ss_family == target->ss_family)&&((*mr)->via().ipsEqual(*via))) || (tapdev == (*mr)->device()) ) ) { + haveRoute = true; + (*mr)->sync(); + break; + } + } + if (haveRoute) + continue; + + // Add and apply new routes + n.managedRoutes.push_back(SharedPtr(new ManagedRoute(*target,*via,tapdev))); + if (!n.managedRoutes.back()->sync()) + n.managedRoutes.pop_back(); + } + } + } + + // ========================================================================= + // Handlers for Node and Phy<> callbacks + // ========================================================================= + + inline void phyOnDatagram(PhySocket *sock,void **uptr,const struct sockaddr *localAddr,const struct sockaddr *from,void *data,unsigned long len) + { +#ifdef ZT_ENABLE_CLUSTER + if (sock == _clusterMessageSocket) { + _lastDirectReceiveFromGlobal = OSUtils::now(); + _node->clusterHandleIncomingMessage(data,len); + return; + } +#endif + +#ifdef ZT_BREAK_UDP + if (OSUtils::fileExists("/tmp/ZT_BREAK_UDP")) + return; +#endif + + if ((len >= 16)&&(reinterpret_cast(from)->ipScope() == InetAddress::IP_SCOPE_GLOBAL)) + _lastDirectReceiveFromGlobal = OSUtils::now(); + + const ZT_ResultCode rc = _node->processWirePacket( + (void *)0, + OSUtils::now(), + reinterpret_cast(localAddr), + (const struct sockaddr_storage *)from, // Phy<> uses sockaddr_storage, so it'll always be that big + data, + len, + &_nextBackgroundTaskDeadline); + if (ZT_ResultCode_isFatal(rc)) { + char tmp[256]; + Utils::snprintf(tmp,sizeof(tmp),"fatal error code from processWirePacket: %d",(int)rc); + Mutex::Lock _l(_termReason_m); + _termReason = ONE_UNRECOVERABLE_ERROR; + _fatalErrorMessage = tmp; + this->terminate(); + } + } + + inline void phyOnTcpConnect(PhySocket *sock,void **uptr,bool success) + { + if (!success) + return; + + // Outgoing TCP connections are always TCP fallback tunnel connections. + + TcpConnection *tc = new TcpConnection(); + _tcpConnections.insert(tc); + + tc->type = TcpConnection::TCP_TUNNEL_OUTGOING; + tc->shouldKeepAlive = true; + tc->parent = this; + tc->sock = sock; + // from and parser are not used + tc->messageSize = 0; // unused + tc->lastActivity = OSUtils::now(); + // HTTP stuff is not used + tc->writeBuf = ""; + *uptr = (void *)tc; + + // Send "hello" message + tc->writeBuf.push_back((char)0x17); + tc->writeBuf.push_back((char)0x03); + tc->writeBuf.push_back((char)0x03); // fake TLS 1.2 header + tc->writeBuf.push_back((char)0x00); + tc->writeBuf.push_back((char)0x04); // mlen == 4 + tc->writeBuf.push_back((char)ZEROTIER_ONE_VERSION_MAJOR); + tc->writeBuf.push_back((char)ZEROTIER_ONE_VERSION_MINOR); + tc->writeBuf.push_back((char)((ZEROTIER_ONE_VERSION_REVISION >> 8) & 0xff)); + tc->writeBuf.push_back((char)(ZEROTIER_ONE_VERSION_REVISION & 0xff)); + _phy.setNotifyWritable(sock,true); + + _tcpFallbackTunnel = tc; + } + + inline void phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from) + { + if (!from) { + _phy.close(sockN,false); + return; + } else { + TcpConnection *tc = new TcpConnection(); + _tcpConnections.insert(tc); + tc->type = TcpConnection::TCP_HTTP_INCOMING; + tc->shouldKeepAlive = true; + tc->parent = this; + tc->sock = sockN; + tc->from = from; + http_parser_init(&(tc->parser),HTTP_REQUEST); + tc->parser.data = (void *)tc; + tc->messageSize = 0; + tc->lastActivity = OSUtils::now(); + tc->currentHeaderField = ""; + tc->currentHeaderValue = ""; + tc->url = ""; + tc->status = ""; + tc->headers.clear(); + tc->body = ""; + tc->writeBuf = ""; + *uptrN = (void *)tc; + } + } + + inline void phyOnTcpClose(PhySocket *sock,void **uptr) + { + TcpConnection *tc = (TcpConnection *)*uptr; + if (tc) { + if (tc == _tcpFallbackTunnel) + _tcpFallbackTunnel = (TcpConnection *)0; + _tcpConnections.erase(tc); + delete tc; + } + } + + inline void phyOnTcpData(PhySocket *sock,void **uptr,void *data,unsigned long len) + { + TcpConnection *tc = reinterpret_cast(*uptr); + switch(tc->type) { + + case TcpConnection::TCP_HTTP_INCOMING: + case TcpConnection::TCP_HTTP_OUTGOING: + http_parser_execute(&(tc->parser),&HTTP_PARSER_SETTINGS,(const char *)data,len); + if ((tc->parser.upgrade)||(tc->parser.http_errno != HPE_OK)) { + _phy.close(sock); + return; + } + break; + + case TcpConnection::TCP_TUNNEL_OUTGOING: + tc->body.append((const char *)data,len); + while (tc->body.length() >= 5) { + const char *data = tc->body.data(); + const unsigned long mlen = ( ((((unsigned long)data[3]) & 0xff) << 8) | (((unsigned long)data[4]) & 0xff) ); + if (tc->body.length() >= (mlen + 5)) { + InetAddress from; + + unsigned long plen = mlen; // payload length, modified if there's an IP header + data += 5; // skip forward past pseudo-TLS junk and mlen + if (plen == 4) { + // Hello message, which isn't sent by proxy and would be ignored by client + } else if (plen) { + // Messages should contain IPv4 or IPv6 source IP address data + switch(data[0]) { + case 4: // IPv4 + if (plen >= 7) { + from.set((const void *)(data + 1),4,((((unsigned int)data[5]) & 0xff) << 8) | (((unsigned int)data[6]) & 0xff)); + data += 7; // type + 4 byte IP + 2 byte port + plen -= 7; + } else { + _phy.close(sock); + return; + } + break; + case 6: // IPv6 + if (plen >= 19) { + from.set((const void *)(data + 1),16,((((unsigned int)data[17]) & 0xff) << 8) | (((unsigned int)data[18]) & 0xff)); + data += 19; // type + 16 byte IP + 2 byte port + plen -= 19; + } else { + _phy.close(sock); + return; + } + break; + case 0: // none/omitted + ++data; + --plen; + break; + default: // invalid address type + _phy.close(sock); + return; + } + + if (from) { + InetAddress fakeTcpLocalInterfaceAddress((uint32_t)0xffffffff,0xffff); + const ZT_ResultCode rc = _node->processWirePacket( + (void *)0, + OSUtils::now(), + reinterpret_cast(&fakeTcpLocalInterfaceAddress), + reinterpret_cast(&from), + data, + plen, + &_nextBackgroundTaskDeadline); + if (ZT_ResultCode_isFatal(rc)) { + char tmp[256]; + Utils::snprintf(tmp,sizeof(tmp),"fatal error code from processWirePacket: %d",(int)rc); + Mutex::Lock _l(_termReason_m); + _termReason = ONE_UNRECOVERABLE_ERROR; + _fatalErrorMessage = tmp; + this->terminate(); + _phy.close(sock); + return; + } + } + } + + if (tc->body.length() > (mlen + 5)) + tc->body = tc->body.substr(mlen + 5); + else tc->body = ""; + } else break; + } + break; + + } + } + + inline void phyOnTcpWritable(PhySocket *sock,void **uptr) + { + TcpConnection *tc = reinterpret_cast(*uptr); + Mutex::Lock _l(tc->writeBuf_m); + if (tc->writeBuf.length() > 0) { + long sent = (long)_phy.streamSend(sock,tc->writeBuf.data(),(unsigned long)tc->writeBuf.length(),true); + if (sent > 0) { + tc->lastActivity = OSUtils::now(); + if ((unsigned long)sent >= (unsigned long)tc->writeBuf.length()) { + tc->writeBuf = ""; + _phy.setNotifyWritable(sock,false); + if (!tc->shouldKeepAlive) + _phy.close(sock); // will call close handler to delete from _tcpConnections + } else { + tc->writeBuf = tc->writeBuf.substr(sent); + } + } + } else { + _phy.setNotifyWritable(sock,false); + } + } + + inline void phyOnFileDescriptorActivity(PhySocket *sock,void **uptr,bool readable,bool writable) {} + inline void phyOnUnixAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN) {} + inline void phyOnUnixClose(PhySocket *sock,void **uptr) {} + inline void phyOnUnixData(PhySocket *sock,void **uptr,void *data,unsigned long len) {} + inline void phyOnUnixWritable(PhySocket *sock,void **uptr,bool lwip_invoked) {} + + inline int nodeVirtualNetworkConfigFunction(uint64_t nwid,void **nuptr,enum ZT_VirtualNetworkConfigOperation op,const ZT_VirtualNetworkConfig *nwc) + { + Mutex::Lock _l(_nets_m); + NetworkState &n = _nets[nwid]; + + switch(op) { + + case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_UP: + if (!n.tap) { + try { + char friendlyName[128]; + Utils::snprintf(friendlyName,sizeof(friendlyName),"ZeroTier One [%.16llx]",nwid); + + n.tap = new EthernetTap( + _homePath.c_str(), + MAC(nwc->mac), + nwc->mtu, + (unsigned int)ZT_IF_METRIC, + nwid, + friendlyName, + StapFrameHandler, + (void *)this); + *nuptr = (void *)&n; + + char nlcpath[256]; + Utils::snprintf(nlcpath,sizeof(nlcpath),"%s" ZT_PATH_SEPARATOR_S "networks.d" ZT_PATH_SEPARATOR_S "%.16llx.local.conf",_homePath.c_str(),nwid); + std::string nlcbuf; + if (OSUtils::readFile(nlcpath,nlcbuf)) { + Dictionary<4096> nc; + nc.load(nlcbuf.c_str()); + Buffer<1024> allowManaged; + if (nc.get("allowManaged", allowManaged) && allowManaged.size() != 0) { + std::string addresses (allowManaged.begin(), allowManaged.size()); + if (allowManaged.size() <= 5) { // untidy parsing for backward compatibility + if (allowManaged[0] == '1' || allowManaged[0] == 't' || allowManaged[0] == 'T') { + n.settings.allowManaged = true; + } else { + n.settings.allowManaged = false; + } + } else { + // this should be a list of IP addresses + n.settings.allowManaged = true; + size_t pos = 0; + while (true) { + size_t nextPos = addresses.find(',', pos); + std::string address = addresses.substr(pos, (nextPos == std::string::npos ? addresses.size() : nextPos) - pos); + n.settings.allowManagedWhitelist.push_back(InetAddress(address)); + if (nextPos == std::string::npos) break; + pos = nextPos + 1; + } + } + } else { + n.settings.allowManaged = true; + } + n.settings.allowGlobal = nc.getB("allowGlobal", false); + n.settings.allowDefault = nc.getB("allowDefault", false); + } + } catch (std::exception &exc) { +#ifdef __WINDOWS__ + FILE *tapFailLog = fopen((_homePath + ZT_PATH_SEPARATOR_S"port_error_log.txt").c_str(),"a"); + if (tapFailLog) { + fprintf(tapFailLog,"%.16llx: %s" ZT_EOL_S,(unsigned long long)nwid,exc.what()); + fclose(tapFailLog); + } +#else + fprintf(stderr,"ERROR: unable to configure virtual network port: %s" ZT_EOL_S,exc.what()); +#endif + _nets.erase(nwid); + return -999; + } catch ( ... ) { + return -999; // tap init failed + } + } + // After setting up tap, fall through to CONFIG_UPDATE since we also want to do this... + + case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE: + memcpy(&(n.config),nwc,sizeof(ZT_VirtualNetworkConfig)); + if (n.tap) { // sanity check +#ifdef __WINDOWS__ + // wait for up to 5 seconds for the WindowsEthernetTap to actually be initialized + // + // without WindowsEthernetTap::isInitialized() returning true, the won't actually + // be online yet and setting managed routes on it will fail. + const int MAX_SLEEP_COUNT = 500; + for (int i = 0; !n.tap->isInitialized() && i < MAX_SLEEP_COUNT; i++) { + Sleep(10); + } +#endif + syncManagedStuff(n,true,true); + } else { + _nets.erase(nwid); + return -999; // tap init failed + } + break; + + case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN: + case ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY: + if (n.tap) { // sanity check +#ifdef __WINDOWS__ + std::string winInstanceId(n.tap->instanceId()); +#endif + *nuptr = (void *)0; + delete n.tap; + _nets.erase(nwid); +#ifdef __WINDOWS__ + if ((op == ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY)&&(winInstanceId.length() > 0)) + WindowsEthernetTap::deletePersistentTapDevice(winInstanceId.c_str()); +#endif + if (op == ZT_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY) { + char nlcpath[256]; + Utils::snprintf(nlcpath,sizeof(nlcpath),"%s" ZT_PATH_SEPARATOR_S "networks.d" ZT_PATH_SEPARATOR_S "%.16llx.local.conf",_homePath.c_str(),nwid); + OSUtils::rm(nlcpath); + } + } else { + _nets.erase(nwid); + } + break; + + } + return 0; + } + + inline void nodeEventCallback(enum ZT_Event event,const void *metaData) + { + switch(event) { + case ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION: { + Mutex::Lock _l(_termReason_m); + _termReason = ONE_IDENTITY_COLLISION; + _fatalErrorMessage = "identity/address collision"; + this->terminate(); + } break; + + case ZT_EVENT_TRACE: { + if (metaData) { + ::fprintf(stderr,"%s" ZT_EOL_S,(const char *)metaData); + ::fflush(stderr); + } + } break; + + case ZT_EVENT_USER_MESSAGE: { + const ZT_UserMessage *um = reinterpret_cast(metaData); + if ((um->typeId == ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE)&&(_updater)) { + _updater->handleSoftwareUpdateUserMessage(um->origin,um->data,um->length); + } + } break; + + default: + break; + } + } + + inline long nodeDataStoreGetFunction(const char *name,void *buf,unsigned long bufSize,unsigned long readIndex,unsigned long *totalSize) + { + std::string p(_dataStorePrepPath(name)); + if (!p.length()) + return -2; + + FILE *f = fopen(p.c_str(),"rb"); + if (!f) + return -1; + if (fseek(f,0,SEEK_END) != 0) { + fclose(f); + return -2; + } + long ts = ftell(f); + if (ts < 0) { + fclose(f); + return -2; + } + *totalSize = (unsigned long)ts; + if (fseek(f,(long)readIndex,SEEK_SET) != 0) { + fclose(f); + return -2; + } + long n = (long)fread(buf,1,bufSize,f); + fclose(f); + return n; + } + + inline int nodeDataStorePutFunction(const char *name,const void *data,unsigned long len,int secure) + { + std::string p(_dataStorePrepPath(name)); + if (!p.length()) + return -2; + + if (!data) { + OSUtils::rm(p.c_str()); + return 0; + } + + FILE *f = fopen(p.c_str(),"wb"); + if (!f) + return -1; + if (fwrite(data,len,1,f) == 1) { + fclose(f); + if (secure) + OSUtils::lockDownFile(p.c_str(),false); + return 0; + } else { + fclose(f); + OSUtils::rm(p.c_str()); + return -1; + } + } + + inline int nodeWirePacketSendFunction(const struct sockaddr_storage *localAddr,const struct sockaddr_storage *addr,const void *data,unsigned int len,unsigned int ttl) + { + unsigned int fromBindingNo = 0; + + if (addr->ss_family == AF_INET) { + if (reinterpret_cast(localAddr)->sin_port == 0) { + // If sender is sending from wildcard (null address), choose the secondary backup + // port 1/4 of the time. (but only for IPv4) + fromBindingNo = (++_udpPortPickerCounter & 0x4) >> 2; + if (!_ports[fromBindingNo]) + fromBindingNo = 0; + } else { + const uint16_t lp = reinterpret_cast(localAddr)->sin_port; + if (lp == _portsBE[1]) + fromBindingNo = 1; + else if (lp == _portsBE[2]) + fromBindingNo = 2; + } + +#ifdef ZT_TCP_FALLBACK_RELAY + // TCP fallback tunnel support, currently IPv4 only + if ((len >= 16)&&(reinterpret_cast(addr)->ipScope() == InetAddress::IP_SCOPE_GLOBAL)) { + // Engage TCP tunnel fallback if we haven't received anything valid from a global + // IP address in ZT_TCP_FALLBACK_AFTER milliseconds. If we do start getting + // valid direct traffic we'll stop using it and close the socket after a while. + const uint64_t now = OSUtils::now(); + if (((now - _lastDirectReceiveFromGlobal) > ZT_TCP_FALLBACK_AFTER)&&((now - _lastRestart) > ZT_TCP_FALLBACK_AFTER)) { + if (_tcpFallbackTunnel) { + Mutex::Lock _l(_tcpFallbackTunnel->writeBuf_m); + if (!_tcpFallbackTunnel->writeBuf.length()) + _phy.setNotifyWritable(_tcpFallbackTunnel->sock,true); + unsigned long mlen = len + 7; + _tcpFallbackTunnel->writeBuf.push_back((char)0x17); + _tcpFallbackTunnel->writeBuf.push_back((char)0x03); + _tcpFallbackTunnel->writeBuf.push_back((char)0x03); // fake TLS 1.2 header + _tcpFallbackTunnel->writeBuf.push_back((char)((mlen >> 8) & 0xff)); + _tcpFallbackTunnel->writeBuf.push_back((char)(mlen & 0xff)); + _tcpFallbackTunnel->writeBuf.push_back((char)4); // IPv4 + _tcpFallbackTunnel->writeBuf.append(reinterpret_cast(reinterpret_cast(&(reinterpret_cast(addr)->sin_addr.s_addr))),4); + _tcpFallbackTunnel->writeBuf.append(reinterpret_cast(reinterpret_cast(&(reinterpret_cast(addr)->sin_port))),2); + _tcpFallbackTunnel->writeBuf.append((const char *)data,len); + } else if (((now - _lastSendToGlobalV4) < ZT_TCP_FALLBACK_AFTER)&&((now - _lastSendToGlobalV4) > (ZT_PING_CHECK_INVERVAL / 2))) { + bool connected = false; + const InetAddress addr(ZT_TCP_FALLBACK_RELAY); + _phy.tcpConnect(reinterpret_cast(&addr),connected); + } + } + _lastSendToGlobalV4 = now; + } +#endif // ZT_TCP_FALLBACK_RELAY + } else if (addr->ss_family == AF_INET6) { + if (reinterpret_cast(localAddr)->sin6_port != 0) { + const uint16_t lp = reinterpret_cast(localAddr)->sin6_port; + if (lp == _portsBE[1]) + fromBindingNo = 1; + else if (lp == _portsBE[2]) + fromBindingNo = 2; + } + } else { + return -1; + } + +#ifdef ZT_BREAK_UDP + if (OSUtils::fileExists("/tmp/ZT_BREAK_UDP")) + return 0; // silently break UDP +#endif + + return (_bindings[fromBindingNo].udpSend(_phy,*(reinterpret_cast(localAddr)),*(reinterpret_cast(addr)),data,len,ttl)) ? 0 : -1; + } + + inline void nodeVirtualNetworkFrameFunction(uint64_t nwid,void **nuptr,uint64_t sourceMac,uint64_t destMac,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) + { + NetworkState *n = reinterpret_cast(*nuptr); + if ((!n)||(!n->tap)) + return; + n->tap->put(MAC(sourceMac),MAC(destMac),etherType,data,len); + } + + inline int nodePathCheckFunction(uint64_t ztaddr,const struct sockaddr_storage *localAddr,const struct sockaddr_storage *remoteAddr) + { + // Make sure we're not trying to do ZeroTier-over-ZeroTier + { + Mutex::Lock _l(_nets_m); + for(std::map::const_iterator n(_nets.begin());n!=_nets.end();++n) { + if (n->second.tap) { + std::vector ips(n->second.tap->ips()); + for(std::vector::const_iterator i(ips.begin());i!=ips.end();++i) { + if (i->containsAddress(*(reinterpret_cast(remoteAddr)))) { + return 0; + } + } + } + } + } + + /* Note: I do not think we need to scan for overlap with managed routes + * because of the "route forking" and interface binding that we do. This + * ensures (we hope) that ZeroTier traffic will still take the physical + * path even if its managed routes override this for other traffic. Will + * revisit if we see recursion problems. */ + + // Check blacklists + const Hashtable< uint64_t,std::vector > *blh = (const Hashtable< uint64_t,std::vector > *)0; + const std::vector *gbl = (const std::vector *)0; + if (remoteAddr->ss_family == AF_INET) { + blh = &_v4Blacklists; + gbl = &_globalV4Blacklist; + } else if (remoteAddr->ss_family == AF_INET6) { + blh = &_v6Blacklists; + gbl = &_globalV6Blacklist; + } + if (blh) { + Mutex::Lock _l(_localConfig_m); + const std::vector *l = blh->get(ztaddr); + if (l) { + for(std::vector::const_iterator a(l->begin());a!=l->end();++a) { + if (a->containsAddress(*reinterpret_cast(remoteAddr))) + return 0; + } + } + for(std::vector::const_iterator a(gbl->begin());a!=gbl->end();++a) { + if (a->containsAddress(*reinterpret_cast(remoteAddr))) + return 0; + } + } + + return 1; + } + + inline int nodePathLookupFunction(uint64_t ztaddr,int family,struct sockaddr_storage *result) + { + const Hashtable< uint64_t,std::vector > *lh = (const Hashtable< uint64_t,std::vector > *)0; + if (family < 0) + lh = (_node->prng() & 1) ? &_v4Hints : &_v6Hints; + else if (family == AF_INET) + lh = &_v4Hints; + else if (family == AF_INET6) + lh = &_v6Hints; + else return 0; + const std::vector *l = lh->get(ztaddr); + if ((l)&&(l->size() > 0)) { + memcpy(result,&((*l)[(unsigned long)_node->prng() % l->size()]),sizeof(struct sockaddr_storage)); + return 1; + } else return 0; + } + + inline void tapFrameHandler(uint64_t nwid,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) + { + _node->processVirtualNetworkFrame((void *)0,OSUtils::now(),nwid,from.toInt(),to.toInt(),etherType,vlanId,data,len,&_nextBackgroundTaskDeadline); + } + + inline void onHttpRequestToServer(TcpConnection *tc) + { + char tmpn[256]; + std::string data; + std::string contentType("text/plain"); // default if not changed in handleRequest() + unsigned int scode = 404; + + bool allow; + { + Mutex::Lock _l(_localConfig_m); + if (_allowManagementFrom.size() == 0) { + allow = (tc->from.ipScope() == InetAddress::IP_SCOPE_LOOPBACK); + } else { + allow = false; + for(std::vector::const_iterator i(_allowManagementFrom.begin());i!=_allowManagementFrom.end();++i) { + if (i->containsAddress(tc->from)) { + allow = true; + break; + } + } + } + } + + if (allow) { + try { + scode = handleControlPlaneHttpRequest(tc->from,tc->parser.method,tc->url,tc->headers,tc->body,data,contentType); + } catch (std::exception &exc) { + fprintf(stderr,"WARNING: unexpected exception processing control HTTP request: %s" ZT_EOL_S,exc.what()); + scode = 500; + } catch ( ... ) { + fprintf(stderr,"WARNING: unexpected exception processing control HTTP request: unknown exceptino" ZT_EOL_S); + scode = 500; + } + } else { + scode = 401; + } + + const char *scodestr; + switch(scode) { + case 200: scodestr = "OK"; break; + case 400: scodestr = "Bad Request"; break; + case 401: scodestr = "Unauthorized"; break; + case 403: scodestr = "Forbidden"; break; + case 404: scodestr = "Not Found"; break; + case 500: scodestr = "Internal Server Error"; break; + case 501: scodestr = "Not Implemented"; break; + case 503: scodestr = "Service Unavailable"; break; + default: scodestr = "Error"; break; + } + + Utils::snprintf(tmpn,sizeof(tmpn),"HTTP/1.1 %.3u %s\r\nCache-Control: no-cache\r\nPragma: no-cache\r\n",scode,scodestr); + { + Mutex::Lock _l(tc->writeBuf_m); + tc->writeBuf.assign(tmpn); + tc->writeBuf.append("Content-Type: "); + tc->writeBuf.append(contentType); + Utils::snprintf(tmpn,sizeof(tmpn),"\r\nContent-Length: %lu\r\n",(unsigned long)data.length()); + tc->writeBuf.append(tmpn); + if (!tc->shouldKeepAlive) + tc->writeBuf.append("Connection: close\r\n"); + tc->writeBuf.append("\r\n"); + if (tc->parser.method != HTTP_HEAD) + tc->writeBuf.append(data); + } + + _phy.setNotifyWritable(tc->sock,true); + } + + inline void onHttpResponseFromClient(TcpConnection *tc) + { + if (!tc->shouldKeepAlive) + _phy.close(tc->sock); // will call close handler, which deletes from _tcpConnections + } + + bool shouldBindInterface(const char *ifname,const InetAddress &ifaddr) + { +#if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) + if ((ifname[0] == 'l')&&(ifname[1] == 'o')) return false; // loopback + if ((ifname[0] == 'z')&&(ifname[1] == 't')) return false; // sanity check: zt# + if ((ifname[0] == 't')&&(ifname[1] == 'u')&&(ifname[2] == 'n')) return false; // tun# is probably an OpenVPN tunnel or similar + if ((ifname[0] == 't')&&(ifname[1] == 'a')&&(ifname[2] == 'p')) return false; // tap# is probably an OpenVPN tunnel or similar +#endif + +#ifdef __APPLE__ + if ((ifname[0] == 'l')&&(ifname[1] == 'o')) return false; // loopback + if ((ifname[0] == 'z')&&(ifname[1] == 't')) return false; // sanity check: zt# + if ((ifname[0] == 't')&&(ifname[1] == 'u')&&(ifname[2] == 'n')) return false; // tun# is probably an OpenVPN tunnel or similar + if ((ifname[0] == 't')&&(ifname[1] == 'a')&&(ifname[2] == 'p')) return false; // tap# is probably an OpenVPN tunnel or similar + if ((ifname[0] == 'u')&&(ifname[1] == 't')&&(ifname[2] == 'u')&&(ifname[3] == 'n')) return false; // ... as is utun# +#endif + + { + Mutex::Lock _l(_localConfig_m); + for(std::vector::const_iterator p(_interfacePrefixBlacklist.begin());p!=_interfacePrefixBlacklist.end();++p) { + if (!strncmp(p->c_str(),ifname,p->length())) + return false; + } + } + + { + Mutex::Lock _l(_nets_m); + for(std::map::const_iterator n(_nets.begin());n!=_nets.end();++n) { + if (n->second.tap) { + std::vector ips(n->second.tap->ips()); + for(std::vector::const_iterator i(ips.begin());i!=ips.end();++i) { + if (i->ipsEqual(ifaddr)) + return false; + } + } + } + } + + return true; + } + + std::string _dataStorePrepPath(const char *name) const + { + std::string p(_homePath); + p.push_back(ZT_PATH_SEPARATOR); + char lastc = (char)0; + for(const char *n=name;(*n);++n) { + if ((*n == '.')&&(lastc == '.')) + return std::string(); // don't allow ../../ stuff as a precaution + if (*n == '/') { + OSUtils::mkdir(p.c_str()); + p.push_back(ZT_PATH_SEPARATOR); + } else p.push_back(*n); + lastc = *n; + } + return p; + } + + bool _trialBind(unsigned int port) + { + struct sockaddr_in in4; + struct sockaddr_in6 in6; + PhySocket *tb; + + memset(&in4,0,sizeof(in4)); + in4.sin_family = AF_INET; + in4.sin_port = Utils::hton((uint16_t)port); + tb = _phy.udpBind(reinterpret_cast(&in4),(void *)0,0); + if (tb) { + _phy.close(tb,false); + tb = _phy.tcpListen(reinterpret_cast(&in4),(void *)0); + if (tb) { + _phy.close(tb,false); + return true; + } + } + + memset(&in6,0,sizeof(in6)); + in6.sin6_family = AF_INET6; + in6.sin6_port = Utils::hton((uint16_t)port); + tb = _phy.udpBind(reinterpret_cast(&in6),(void *)0,0); + if (tb) { + _phy.close(tb,false); + tb = _phy.tcpListen(reinterpret_cast(&in6),(void *)0); + if (tb) { + _phy.close(tb,false); + return true; + } + } + + return false; + } +}; + +static int SnodeVirtualNetworkConfigFunction(ZT_Node *node,void *uptr,void *tptr,uint64_t nwid,void **nuptr,enum ZT_VirtualNetworkConfigOperation op,const ZT_VirtualNetworkConfig *nwconf) +{ return reinterpret_cast(uptr)->nodeVirtualNetworkConfigFunction(nwid,nuptr,op,nwconf); } +static void SnodeEventCallback(ZT_Node *node,void *uptr,void *tptr,enum ZT_Event event,const void *metaData) +{ reinterpret_cast(uptr)->nodeEventCallback(event,metaData); } +static long SnodeDataStoreGetFunction(ZT_Node *node,void *uptr,void *tptr,const char *name,void *buf,unsigned long bufSize,unsigned long readIndex,unsigned long *totalSize) +{ return reinterpret_cast(uptr)->nodeDataStoreGetFunction(name,buf,bufSize,readIndex,totalSize); } +static int SnodeDataStorePutFunction(ZT_Node *node,void *uptr,void *tptr,const char *name,const void *data,unsigned long len,int secure) +{ return reinterpret_cast(uptr)->nodeDataStorePutFunction(name,data,len,secure); } +static int SnodeWirePacketSendFunction(ZT_Node *node,void *uptr,void *tptr,const struct sockaddr_storage *localAddr,const struct sockaddr_storage *addr,const void *data,unsigned int len,unsigned int ttl) +{ return reinterpret_cast(uptr)->nodeWirePacketSendFunction(localAddr,addr,data,len,ttl); } +static void SnodeVirtualNetworkFrameFunction(ZT_Node *node,void *uptr,void *tptr,uint64_t nwid,void **nuptr,uint64_t sourceMac,uint64_t destMac,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) +{ reinterpret_cast(uptr)->nodeVirtualNetworkFrameFunction(nwid,nuptr,sourceMac,destMac,etherType,vlanId,data,len); } +static int SnodePathCheckFunction(ZT_Node *node,void *uptr,void *tptr,uint64_t ztaddr,const struct sockaddr_storage *localAddr,const struct sockaddr_storage *remoteAddr) +{ return reinterpret_cast(uptr)->nodePathCheckFunction(ztaddr,localAddr,remoteAddr); } +static int SnodePathLookupFunction(ZT_Node *node,void *uptr,void *tptr,uint64_t ztaddr,int family,struct sockaddr_storage *result) +{ return reinterpret_cast(uptr)->nodePathLookupFunction(ztaddr,family,result); } + +#ifdef ZT_ENABLE_CLUSTER +static void SclusterSendFunction(void *uptr,unsigned int toMemberId,const void *data,unsigned int len) +{ + OneServiceImpl *const impl = reinterpret_cast(uptr); + const ClusterDefinition::MemberDefinition &md = (*(impl->_clusterDefinition))[toMemberId]; + if (md.clusterEndpoint) + impl->_phy.udpSend(impl->_clusterMessageSocket,reinterpret_cast(&(md.clusterEndpoint)),data,len); +} +static int SclusterGeoIpFunction(void *uptr,const struct sockaddr_storage *addr,int *x,int *y,int *z) +{ + OneServiceImpl *const impl = reinterpret_cast(uptr); + return (int)(impl->_clusterDefinition->geo().locate(*(reinterpret_cast(addr)),*x,*y,*z)); +} +#endif + +static void StapFrameHandler(void *uptr,void *tptr,uint64_t nwid,const MAC &from,const MAC &to,unsigned int etherType,unsigned int vlanId,const void *data,unsigned int len) +{ reinterpret_cast(uptr)->tapFrameHandler(nwid,from,to,etherType,vlanId,data,len); } + +static int ShttpOnMessageBegin(http_parser *parser) +{ + TcpConnection *tc = reinterpret_cast(parser->data); + tc->currentHeaderField = ""; + tc->currentHeaderValue = ""; + tc->messageSize = 0; + tc->url = ""; + tc->status = ""; + tc->headers.clear(); + tc->body = ""; + return 0; +} +static int ShttpOnUrl(http_parser *parser,const char *ptr,size_t length) +{ + TcpConnection *tc = reinterpret_cast(parser->data); + tc->messageSize += (unsigned long)length; + if (tc->messageSize > ZT_MAX_HTTP_MESSAGE_SIZE) + return -1; + tc->url.append(ptr,length); + return 0; +} +#if (HTTP_PARSER_VERSION_MAJOR >= 2) && (HTTP_PARSER_VERSION_MINOR >= 2) +static int ShttpOnStatus(http_parser *parser,const char *ptr,size_t length) +#else +static int ShttpOnStatus(http_parser *parser) +#endif +{ + /* + TcpConnection *tc = reinterpret_cast(parser->data); + tc->messageSize += (unsigned long)length; + if (tc->messageSize > ZT_MAX_HTTP_MESSAGE_SIZE) + return -1; + tc->status.append(ptr,length); + */ + return 0; +} +static int ShttpOnHeaderField(http_parser *parser,const char *ptr,size_t length) +{ + TcpConnection *tc = reinterpret_cast(parser->data); + tc->messageSize += (unsigned long)length; + if (tc->messageSize > ZT_MAX_HTTP_MESSAGE_SIZE) + return -1; + if ((tc->currentHeaderField.length())&&(tc->currentHeaderValue.length())) { + tc->headers[tc->currentHeaderField] = tc->currentHeaderValue; + tc->currentHeaderField = ""; + tc->currentHeaderValue = ""; + } + for(size_t i=0;icurrentHeaderField.push_back(OSUtils::toLower(ptr[i])); + return 0; +} +static int ShttpOnValue(http_parser *parser,const char *ptr,size_t length) +{ + TcpConnection *tc = reinterpret_cast(parser->data); + tc->messageSize += (unsigned long)length; + if (tc->messageSize > ZT_MAX_HTTP_MESSAGE_SIZE) + return -1; + tc->currentHeaderValue.append(ptr,length); + return 0; +} +static int ShttpOnHeadersComplete(http_parser *parser) +{ + TcpConnection *tc = reinterpret_cast(parser->data); + if ((tc->currentHeaderField.length())&&(tc->currentHeaderValue.length())) + tc->headers[tc->currentHeaderField] = tc->currentHeaderValue; + return 0; +} +static int ShttpOnBody(http_parser *parser,const char *ptr,size_t length) +{ + TcpConnection *tc = reinterpret_cast(parser->data); + tc->messageSize += (unsigned long)length; + if (tc->messageSize > ZT_MAX_HTTP_MESSAGE_SIZE) + return -1; + tc->body.append(ptr,length); + return 0; +} +static int ShttpOnMessageComplete(http_parser *parser) +{ + TcpConnection *tc = reinterpret_cast(parser->data); + tc->shouldKeepAlive = (http_should_keep_alive(parser) != 0); + tc->lastActivity = OSUtils::now(); + if (tc->type == TcpConnection::TCP_HTTP_INCOMING) { + tc->parent->onHttpRequestToServer(tc); + } else { + tc->parent->onHttpResponseFromClient(tc); + } + return 0; +} + +} // anonymous namespace + +std::string OneService::platformDefaultHomePath() +{ + return OSUtils::platformDefaultHomePath(); +} + +OneService *OneService::newInstance(const char *hp,unsigned int port) { return new OneServiceImpl(hp,port); } +OneService::~OneService() {} + +} // namespace ZeroTier diff --git a/service/OneService.hpp b/service/OneService.hpp new file mode 100644 index 0000000..3390f2a --- /dev/null +++ b/service/OneService.hpp @@ -0,0 +1,172 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_ONESERVICE_HPP +#define ZT_ONESERVICE_HPP + +#include +#include + +#include "../node/InetAddress.hpp" + +namespace ZeroTier { + +/** + * Local service for ZeroTier One as system VPN/NFV provider + */ +class OneService +{ +public: + /** + * Returned by node main if/when it terminates + */ + enum ReasonForTermination + { + /** + * Instance is still running + */ + ONE_STILL_RUNNING = 0, + + /** + * Normal shutdown + */ + ONE_NORMAL_TERMINATION = 1, + + /** + * A serious unrecoverable error has occurred + */ + ONE_UNRECOVERABLE_ERROR = 2, + + /** + * Your identity has collided with another + */ + ONE_IDENTITY_COLLISION = 3 + }; + + /** + * Local settings for each network + */ + struct NetworkSettings + { + /** + * Allow this network to configure IP addresses and routes? + */ + bool allowManaged; + + /** + * Whitelist of addresses that can be configured by this network. + * If empty and allowManaged is true, allow all private/pseudoprivate addresses. + */ + std::vector allowManagedWhitelist; + + /** + * Allow configuration of IPs and routes within global (Internet) IP space? + */ + bool allowGlobal; + + /** + * Allow overriding of system default routes for "full tunnel" operation? + */ + bool allowDefault; + }; + + /** + * @return Platform default home path or empty string if this platform doesn't have one + */ + static std::string platformDefaultHomePath(); + + /** + * Create a new instance of the service + * + * Once created, you must call the run() method to actually start + * processing. + * + * The port is saved to a file in the home path called zerotier-one.port, + * which is used by the CLI and can be used to see which port was chosen if + * 0 (random port) is picked. + * + * @param hp Home path + * @param port TCP and UDP port for packets and HTTP control (if 0, pick random port) + */ + static OneService *newInstance(const char *hp,unsigned int port); + + virtual ~OneService(); + + /** + * Execute the service main I/O loop until terminated + * + * The terminate() method may be called from a signal handler or another + * thread to terminate execution. Otherwise this will not return unless + * another condition terminates execution such as a fatal error. + */ + virtual ReasonForTermination run() = 0; + + /** + * @return Reason for terminating or ONE_STILL_RUNNING if running + */ + virtual ReasonForTermination reasonForTermination() const = 0; + + /** + * @return Fatal error message or empty string if none + */ + virtual std::string fatalErrorMessage() const = 0; + + /** + * @return System device name corresponding with a given ZeroTier network ID or empty string if not opened yet or network ID not found + */ + virtual std::string portDeviceName(uint64_t nwid) const = 0; + + /** + * Terminate background service (can be called from other threads) + */ + virtual void terminate() = 0; + + /** + * Get local settings for a network + * + * @param nwid Network ID + * @param settings Buffer to fill with local network settings + * @return True if network was found and settings is filled + */ + virtual bool getNetworkSettings(const uint64_t nwid,NetworkSettings &settings) const = 0; + + /** + * Set local settings for a network + * + * @param nwid Network ID + * @param settings New network local settings + * @return True if network was found and setting modified + */ + virtual bool setNetworkSettings(const uint64_t nwid,const NetworkSettings &settings) = 0; + + /** + * @return True if service is still running + */ + inline bool isRunning() const { return (this->reasonForTermination() == ONE_STILL_RUNNING); } + +protected: + OneService() {} + +private: + OneService(const OneService &one) {} + inline OneService &operator=(const OneService &one) { return *this; } +}; + +} // namespace ZeroTier + +#endif diff --git a/service/README.md b/service/README.md new file mode 100644 index 0000000..f5223f2 --- /dev/null +++ b/service/README.md @@ -0,0 +1,181 @@ +ZeroTier One Network Virtualization Service +====== + +This is the actual implementation of ZeroTier One, a service providing connectivity to ZeroTier virtual networks for desktops, laptops, servers, VMs, etc. (Mobile versions for iOS and Android have their own implementations in native Java and Objective C that leverage only the ZeroTier core engine.) + +### Local Configuration File + +A file called `local.conf` in the ZeroTier home folder contains configuration options that apply to the local node. It can be used to set up trusted paths, blacklist physical paths, set up physical path hints for certain nodes, and define trusted upstream devices (federated roots). In a large deployment it can be deployed using a tool like Puppet, Chef, SaltStack, etc. to set a uniform configuration across systems. It's a JSON format file that can also be edited and rewritten by ZeroTier One itself, so ensure that proper JSON formatting is used. + +Settings available in `local.conf` (this is not valid JSON, and JSON does not allow comments): + +```javascript +{ + "physical": { /* Settings that apply to physical L2/L3 network paths. */ + "NETWORK/bits": { /* Network e.g. 10.0.0.0/24 or fd00::/32 */ + "blacklist": true|false, /* If true, blacklist this path for all ZeroTier traffic */ + "trustedPathId": 0|!0 /* If present and nonzero, define this as a trusted path (see below) */ + } /* ,... additional networks */ + }, + "virtual": { /* Settings applied to ZeroTier virtual network devices (VL1) */ + "##########": { /* 10-digit ZeroTier address */ + "try": [ "IP/port"/*,...*/ ], /* Hints on where to reach this peer if no upstreams/roots are online */ + "blacklist": [ "NETWORK/bits"/*,...*/ ] /* Blacklist a physical path for only this peer. */ + } + }, + "settings": { /* Other global settings */ + "primaryPort": 0-65535, /* If set, override default port of 9993 and any command line port */ + "portMappingEnabled": true|false, /* If true (the default), try to use uPnP or NAT-PMP to map ports */ + "softwareUpdate": "apply"|"download"|"disable", /* Automatically apply updates, just download, or disable built-in software updates */ + "softwareUpdateChannel": "release"|"beta", /* Software update channel */ + "softwareUpdateDist": true|false, /* If true, distribute software updates (only really useful to ZeroTier, Inc. itself, default is false) */ + "interfacePrefixBlacklist": [ "XXX",... ], /* Array of interface name prefixes (e.g. eth for eth#) to blacklist for ZT traffic */ + "allowManagementFrom": "NETWORK/bits"|null /* If non-NULL, allow JSON/HTTP management from this IP network. Default is 127.0.0.1 only. */ + } +} +``` + + * **trustedPathId**: A trusted path is a physical network over which encryption and authentication are not required. This provides a performance boost but sacrifices all ZeroTier's security features when communicating over this path. Only use this if you know what you are doing and really need the performance! To set up a trusted path, all devices using it *MUST* have the *same trusted path ID* for the same network. Trusted path IDs are arbitrary positive non-zero integers. For example a group of devices on a LAN with IPs in 10.0.0.0/24 could use it as a fast trusted path if they all had the same trusted path ID of "25" defined for that network. + * **relayPolicy**: Under what circumstances should this device relay traffic for other devices? The default is TRUSTED, meaning that we'll only relay for devices we know to be members of a network we have joined. NEVER is the default on mobile devices (iOS/Android) and tells us to never relay traffic. ALWAYS is usually only set for upstreams and roots, allowing them to act as promiscuous relays for anyone who desires it. + +An example `local.conf`: + +```javascript +{ + "physical": { + "10.0.0.0/24": { + "blacklist": true + }, + "10.10.10.0/24": { + "trustedPathId": 101010024 + }, + }, + "virtual": { + "feedbeef12": { + "role": "UPSTREAM", + "try": [ "10.10.20.1/9993" ], + "blacklist": [ "192.168.0.0/24" ] + } + }, + "settings": { + "softwareUpdate": "apply", + "softwraeUpdateChannel": "release" + } +} +``` + +### Network Virtualization Service API + +The JSON API supports GET, POST/PUT, and DELETE. PUT is treated as a synonym for POST. Other methods including HEAD are not supported. + +Values POSTed to the JSON API are *extremely* type sensitive. Things *must* be of the indicated type, otherwise they will be ignored or will generate an error. Anything quoted is a string so booleans and integers must lack quotes. Booleans must be *true* or *false* and nothing else. Integers cannot contain decimal points or they are floats (and vice versa). If something seems to be getting ignored or set to a strange value, or if you receive errors, check the type of all JSON fields you are submitting against the types listed below. Unrecognized fields in JSON objects are also ignored. + +API requests must be authenticated via an authentication token. ZeroTier One saves this token in the *authtoken.secret* file in its working directory. This token may be supplied via the *auth* URL parameter (e.g. '?auth=...') or via the *X-ZT1-Auth* HTTP request header. Static UI pages are the only thing the server will allow without authentication. + +A *jsonp* URL argument may be supplied to request JSONP encapsulation. A JSONP response is sent as a script with its JSON response payload wrapped in a call to the function name supplied as the argument to *jsonp*. + +#### /status + + * Purpose: Get running node status and addressing info + * Methods: GET + * Returns: { object } + +| Field | Type | Description | Writable | +| --------------------- | ------------- | ------------------------------------------------- | -------- | +| address | string | 10-digit hex ZeroTier address of this node | no | +| publicIdentity | string | This node's ZeroTier identity.public | no | +| worldId | integer | ZeroTier world ID (never changes except for test) | no | +| worldTimestamp | integer | Timestamp of most recent world definition | no | +| online | boolean | If true at least one upstream peer is reachable | no | +| tcpFallbackActive | boolean | If true we are using slow TCP fallback | no | +| relayPolicy | string | Relay policy: ALWAYS, TRUSTED, or NEVER | no | +| versionMajor | integer | Software major version | no | +| versionMinor | integer | Software minor version | no | +| versionRev | integer | Software revision | no | +| version | string | major.minor.revision | no | +| clock | integer | Current system clock at node (ms since epoch) | no | + +#### /network + + * Purpose: Get all network memberships + * Methods: GET + * Returns: [ {object}, ... ] + +Getting /network returns an array of all networks that this node has joined. See below for network object format. + +#### /network/\ + + * Purpose: Get, join, or leave a network + * Methods: GET, POST, DELETE + * Returns: { object } + +To join a network, POST to it. Since networks have no mandatory writable parameters, POST data is optional and may be omitted. Example: POST to /network/8056c2e21c000001 to join the public "Earth" network. To leave a network, DELETE it e.g. DELETE /network/8056c2e21c000001. + +Most network settings are not writable, as they are defined by the network controller. + +| Field | Type | Description | Writable | +| --------------------- | ------------- | ------------------------------------------------- | -------- | +| id | string | 16-digit hex network ID | no | +| nwid | string | 16-digit hex network ID (legacy field) | no | +| mac | string | MAC address of network device for this network | no | +| name | string | Short name of this network (from controller) | no | +| status | string | Network status (OK, ACCESS_DENIED, etc.) | no | +| type | string | Network type (PUBLIC or PRIVATE) | no | +| mtu | integer | Ethernet MTU | no | +| dhcp | boolean | If true, DHCP should be used to get IP info | no | +| bridge | boolean | If true, this device can bridge others | no | +| broadcastEnabled | boolean | If true ff:ff:ff:ff:ff:ff broadcasts work | no | +| portError | integer | Error code returned by underlying tap driver | no | +| netconfRevision | integer | Network configuration revision ID | no | +| assignedAddresses | [string] | Array of ZeroTier-assigned IP addresses (/bits) | no | +| routes | [object] | Array of ZeroTier-assigned routes (see below) | no | +| portDeviceName | string | Name of virtual network device (if any) | no | +| allowManaged | boolean | Allow IP and route management | yes | +| allowGlobal | boolean | Allow IPs and routes that overlap with global IPs | yes | +| allowDefault | boolean | Allow overriding of system default route | yes | + +Route objects: + +| Field | Type | Description | Writable | +| --------------------- | ------------- | ------------------------------------------------- | -------- | +| target | string | Target network / netmask bits | no | +| via | string | Gateway IP address (next hop) or null for LAN | no | +| flags | integer | Flags, currently always 0 | no | +| metric | integer | Route metric (not currently used) | no | + +#### /peer + + * Purpose: Get all peers + * Methods: GET + * Returns: [ {object}, ... ] + +Getting /peer returns an array of peer objects for all current peers. See below for peer object format. + +#### /peer/\ + + * Purpose: Get or set information about a peer + * Methods: GET, POST + * Returns: { object } + +| Field | Type | Description | Writable | +| --------------------- | ------------- | ------------------------------------------------- | -------- | +| address | string | 10-digit hex ZeroTier address of peer | no | +| versionMajor | integer | Major version of remote (if known) | no | +| versionMinor | integer | Minor version of remote (if known) | no | +| versionRev | integer | Software revision of remote (if known) | no | +| version | string | major.minor.revision | no | +| latency | integer | Latency in milliseconds if known | no | +| role | string | LEAF, UPSTREAM, or ROOT | no | +| paths | [object] | Currently active physical paths (see below) | no | + +Path objects: + +| Field | Type | Description | Writable | +| --------------------- | ------------- | ------------------------------------------------- | -------- | +| address | string | Physical socket address e.g. IP/port | no | +| lastSend | integer | Time of last send through this path | no | +| lastReceive | integer | Time of last receive through this path | no | +| active | boolean | Is this path in use? | no | +| expired | boolean | Is this path expired? | no | +| preferred | boolean | Is this a current preferred path? | no | +| trustedPathId | integer | If nonzero this is a trusted path (unencrypted) | no | diff --git a/service/SoftwareUpdater.cpp b/service/SoftwareUpdater.cpp new file mode 100644 index 0000000..7ec377c --- /dev/null +++ b/service/SoftwareUpdater.cpp @@ -0,0 +1,406 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#include +#include +#include +#include + +#include "../node/Constants.hpp" +#include "../version.h" + +#ifdef __WINDOWS__ +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include "SoftwareUpdater.hpp" + +#include "../node/Utils.hpp" +#include "../node/SHA512.hpp" +#include "../node/Buffer.hpp" +#include "../node/Node.hpp" + +#include "../osdep/OSUtils.hpp" + +namespace ZeroTier { + +SoftwareUpdater::SoftwareUpdater(Node &node,const std::string &homePath) : + _node(node), + _lastCheckTime(0), + _homePath(homePath), + _channel(ZT_SOFTWARE_UPDATE_DEFAULT_CHANNEL), + _distLog((FILE *)0), + _latestValid(false), + _downloadLength(0) +{ + OSUtils::rm((_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME).c_str()); +} + +SoftwareUpdater::~SoftwareUpdater() +{ + if (_distLog) + fclose(_distLog); +} + +void SoftwareUpdater::setUpdateDistribution(bool distribute) +{ + _dist.clear(); + if (distribute) { + _distLog = fopen((_homePath + ZT_PATH_SEPARATOR_S "update-dist.log").c_str(),"a"); + + const std::string udd(_homePath + ZT_PATH_SEPARATOR_S "update-dist.d"); + const std::vector ud(OSUtils::listDirectory(udd.c_str())); + for(std::vector::const_iterator u(ud.begin());u!=ud.end();++u) { + // Each update has a companion .json file describing it. Other files are ignored. + if ((u->length() > 5)&&(u->substr(u->length() - 5,5) == ".json")) { + + std::string buf; + if (OSUtils::readFile((udd + ZT_PATH_SEPARATOR_S + *u).c_str(),buf)) { + try { + _D d; + d.meta = OSUtils::jsonParse(buf); // throws on invalid JSON + + // If update meta is called e.g. foo.exe.json, then foo.exe is the update itself + const std::string binPath(udd + ZT_PATH_SEPARATOR_S + u->substr(0,u->length() - 5)); + const std::string metaHash(OSUtils::jsonBinFromHex(d.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH])); + if ((metaHash.length() == ZT_SHA512_DIGEST_LEN)&&(OSUtils::readFile(binPath.c_str(),d.bin))) { + uint8_t sha512[ZT_SHA512_DIGEST_LEN]; + SHA512::hash(sha512,d.bin.data(),(unsigned int)d.bin.length()); + if (!memcmp(sha512,metaHash.data(),ZT_SHA512_DIGEST_LEN)) { // double check that hash in JSON is correct + d.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIZE] = d.bin.length(); // override with correct value -- setting this in meta json is optional + _dist[Array(sha512)] = d; + if (_distLog) { + fprintf(_distLog,".......... INIT: DISTRIBUTING %s (%u bytes)" ZT_EOL_S,binPath.c_str(),(unsigned int)d.bin.length()); + fflush(_distLog); + } + } + } + } catch ( ... ) {} // ignore bad meta JSON, etc. + } + + } + } + } else { + if (_distLog) { + fclose(_distLog); + _distLog = (FILE *)0; + } + } +} + +void SoftwareUpdater::handleSoftwareUpdateUserMessage(uint64_t origin,const void *data,unsigned int len) +{ + if (!len) return; + const MessageVerb v = (MessageVerb)reinterpret_cast(data)[0]; + try { + switch(v) { + + case VERB_GET_LATEST: + case VERB_LATEST: { + nlohmann::json req = OSUtils::jsonParse(std::string(reinterpret_cast(data) + 1,len - 1)); // throws on invalid JSON + if (req.is_object()) { + const unsigned int rvMaj = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR],0); + const unsigned int rvMin = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR],0); + const unsigned int rvRev = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION],0); + const unsigned int rvBld = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VERSION_BUILD],0); + const unsigned int rvPlatform = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_PLATFORM],0); + const unsigned int rvArch = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE],0); + const unsigned int rvVendor = (unsigned int)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_VENDOR],0); + const std::string rvChannel(OSUtils::jsonString(req[ZT_SOFTWARE_UPDATE_JSON_CHANNEL],"")); + + if (v == VERB_GET_LATEST) { + + if (_dist.size() > 0) { + const nlohmann::json *latest = (const nlohmann::json *)0; + const std::string expectedSigner = OSUtils::jsonString(req[ZT_SOFTWARE_UPDATE_JSON_EXPECT_SIGNED_BY],""); + unsigned int bestVMaj = rvMaj; + unsigned int bestVMin = rvMin; + unsigned int bestVRev = rvRev; + unsigned int bestVBld = rvBld; + for(std::map< Array,_D >::const_iterator d(_dist.begin());d!=_dist.end();++d) { + // The arch field in update description .json files can be an array for e.g. multi-arch update files + const nlohmann::json &dvArch2 = d->second.meta[ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE]; + std::vector dvArch; + if (dvArch2.is_array()) { + for(unsigned long i=0;isecond.meta[ZT_SOFTWARE_UPDATE_JSON_PLATFORM],0) == rvPlatform)&& + (std::find(dvArch.begin(),dvArch.end(),rvArch) != dvArch.end())&& + (OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VENDOR],0) == rvVendor)&& + (OSUtils::jsonString(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_CHANNEL],"") == rvChannel)&& + (OSUtils::jsonString(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNED_BY],"") == expectedSigner)) { + const unsigned int dvMaj = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR],0); + const unsigned int dvMin = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR],0); + const unsigned int dvRev = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION],0); + const unsigned int dvBld = (unsigned int)OSUtils::jsonInt(d->second.meta[ZT_SOFTWARE_UPDATE_JSON_VERSION_BUILD],0); + if (Utils::compareVersion(dvMaj,dvMin,dvRev,dvBld,bestVMaj,bestVMin,bestVRev,bestVBld) > 0) { + latest = &(d->second.meta); + bestVMaj = dvMaj; + bestVMin = dvMin; + bestVRev = dvRev; + bestVBld = dvBld; + } + } + } + if (latest) { + std::string lj; + lj.push_back((char)VERB_LATEST); + lj.append(OSUtils::jsonDump(*latest)); + _node.sendUserMessage((void *)0,origin,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,lj.data(),(unsigned int)lj.length()); + if (_distLog) { + fprintf(_distLog,"%.10llx GET_LATEST %u.%u.%u_%u platform %u arch %u vendor %u channel %s -> LATEST %u.%u.%u_%u" ZT_EOL_S,(unsigned long long)origin,rvMaj,rvMin,rvRev,rvBld,rvPlatform,rvArch,rvVendor,rvChannel.c_str(),bestVMaj,bestVMin,bestVRev,bestVBld); + fflush(_distLog); + } + } + } // else no reply, since we have nothing to distribute + + } else { // VERB_LATEST + + if ((origin == ZT_SOFTWARE_UPDATE_SERVICE)&& + (Utils::compareVersion(rvMaj,rvMin,rvRev,rvBld,ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION,ZEROTIER_ONE_VERSION_BUILD) > 0)&& + (OSUtils::jsonString(req[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNED_BY],"") == ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY)) { + const unsigned long len = (unsigned long)OSUtils::jsonInt(req[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIZE],0); + const std::string hash = OSUtils::jsonBinFromHex(req[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH]); + if ((len <= ZT_SOFTWARE_UPDATE_MAX_SIZE)&&(hash.length() >= 16)) { + if (_latestMeta != req) { + _latestMeta = req; + _latestValid = false; + OSUtils::rm((_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME).c_str()); + _download = std::string(); + memcpy(_downloadHashPrefix.data,hash.data(),16); + _downloadLength = len; + } + + if ((_downloadLength > 0)&&(_download.length() < _downloadLength)) { + Buffer<128> gd; + gd.append((uint8_t)VERB_GET_DATA); + gd.append(_downloadHashPrefix.data,16); + gd.append((uint32_t)_download.length()); + _node.sendUserMessage((void *)0,ZT_SOFTWARE_UPDATE_SERVICE,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,gd.data(),gd.size()); + //printf(">> GET_DATA @%u\n",(unsigned int)_download.length()); + } + } + } + } + + } + } break; + + case VERB_GET_DATA: + if ((len >= 21)&&(_dist.size() > 0)) { + unsigned long idx = (unsigned long)*(reinterpret_cast(data) + 17) << 24; + idx |= (unsigned long)*(reinterpret_cast(data) + 18) << 16; + idx |= (unsigned long)*(reinterpret_cast(data) + 19) << 8; + idx |= (unsigned long)*(reinterpret_cast(data) + 20); + //printf("<< GET_DATA @%u from %.10llx for %s\n",(unsigned int)idx,origin,Utils::hex(reinterpret_cast(data) + 1,16).c_str()); + std::map< Array,_D >::iterator d(_dist.find(Array(reinterpret_cast(data) + 1))); + if ((d != _dist.end())&&(idx < (unsigned long)d->second.bin.length())) { + Buffer buf; + buf.append((uint8_t)VERB_DATA); + buf.append(reinterpret_cast(data) + 1,16); + buf.append((uint32_t)idx); + buf.append(d->second.bin.data() + idx,std::min((unsigned long)ZT_SOFTWARE_UPDATE_CHUNK_SIZE,(unsigned long)(d->second.bin.length() - idx))); + _node.sendUserMessage((void *)0,origin,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,buf.data(),buf.size()); + //printf(">> DATA @%u\n",(unsigned int)idx); + } + } + break; + + case VERB_DATA: + if ((len >= 21)&&(_downloadLength > 0)&&(!memcmp(_downloadHashPrefix.data,reinterpret_cast(data) + 1,16))) { + unsigned long idx = (unsigned long)*(reinterpret_cast(data) + 17) << 24; + idx |= (unsigned long)*(reinterpret_cast(data) + 18) << 16; + idx |= (unsigned long)*(reinterpret_cast(data) + 19) << 8; + idx |= (unsigned long)*(reinterpret_cast(data) + 20); + //printf("<< DATA @%u / %u bytes (we now have %u bytes)\n",(unsigned int)idx,(unsigned int)(len - 21),(unsigned int)_download.length()); + if (idx == (unsigned long)_download.length()) { + _download.append(reinterpret_cast(data) + 21,len - 21); + if (_download.length() < _downloadLength) { + Buffer<128> gd; + gd.append((uint8_t)VERB_GET_DATA); + gd.append(_downloadHashPrefix.data,16); + gd.append((uint32_t)_download.length()); + _node.sendUserMessage((void *)0,ZT_SOFTWARE_UPDATE_SERVICE,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,gd.data(),gd.size()); + //printf(">> GET_DATA @%u\n",(unsigned int)_download.length()); + } + } + } + break; + + default: + if (_distLog) { + fprintf(_distLog,"%.10llx WARNING: bad update message verb==%u length==%u (unrecognized verb)" ZT_EOL_S,(unsigned long long)origin,(unsigned int)v,len); + fflush(_distLog); + } + break; + } + } catch ( ... ) { + if (_distLog) { + fprintf(_distLog,"%.10llx WARNING: bad update message verb==%u length==%u (unexpected exception, likely invalid JSON)" ZT_EOL_S,(unsigned long long)origin,(unsigned int)v,len); + fflush(_distLog); + } + } +} + +bool SoftwareUpdater::check(const uint64_t now) +{ + if ((now - _lastCheckTime) >= ZT_SOFTWARE_UPDATE_CHECK_PERIOD) { + _lastCheckTime = now; + char tmp[512]; + const unsigned int len = Utils::snprintf(tmp,sizeof(tmp), + "%c{\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_VERSION_BUILD "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_EXPECT_SIGNED_BY "\":\"%s\"," + "\"" ZT_SOFTWARE_UPDATE_JSON_PLATFORM "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_VENDOR "\":%d," + "\"" ZT_SOFTWARE_UPDATE_JSON_CHANNEL "\":\"%s\"}", + (char)VERB_GET_LATEST, + ZEROTIER_ONE_VERSION_MAJOR, + ZEROTIER_ONE_VERSION_MINOR, + ZEROTIER_ONE_VERSION_REVISION, + ZEROTIER_ONE_VERSION_BUILD, + ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY, + ZT_BUILD_PLATFORM, + ZT_BUILD_ARCHITECTURE, + (int)ZT_VENDOR_ZEROTIER, + _channel.c_str()); + _node.sendUserMessage((void *)0,ZT_SOFTWARE_UPDATE_SERVICE,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,tmp,len); + //printf(">> GET_LATEST\n"); + } + + if (_latestValid) + return true; + + if (_downloadLength > 0) { + if (_download.length() >= _downloadLength) { + // This is the very important security validation part that makes sure + // this software update doesn't have cooties. + + const std::string binPath(_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME); + try { + // (1) Check the hash itself to make sure the image is basically okay + uint8_t sha512[ZT_SHA512_DIGEST_LEN]; + SHA512::hash(sha512,_download.data(),(unsigned int)_download.length()); + if (Utils::hex(sha512,ZT_SHA512_DIGEST_LEN) == OSUtils::jsonString(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH],"")) { + // (2) Check signature by signing authority + const std::string sig(OSUtils::jsonBinFromHex(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNATURE])); + if (Identity(ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY).verify(_download.data(),(unsigned int)_download.length(),sig.data(),(unsigned int)sig.length())) { + // (3) Try to save file, and if so we are good. + OSUtils::rm(binPath.c_str()); + if (OSUtils::writeFile(binPath.c_str(),_download)) { + OSUtils::lockDownFile(binPath.c_str(),false); + _latestValid = true; + //printf("VALID UPDATE\n%s\n",OSUtils::jsonDump(_latestMeta).c_str()); + _download = std::string(); + _downloadLength = 0; + return true; + } + } + } + } catch ( ... ) {} // any exception equals verification failure + + // If we get here, checks failed. + //printf("INVALID UPDATE (!!!)\n%s\n",OSUtils::jsonDump(_latestMeta).c_str()); + OSUtils::rm(binPath.c_str()); + _latestMeta = nlohmann::json(); + _latestValid = false; + _download = std::string(); + _downloadLength = 0; + } else { + Buffer<128> gd; + gd.append((uint8_t)VERB_GET_DATA); + gd.append(_downloadHashPrefix.data,16); + gd.append((uint32_t)_download.length()); + _node.sendUserMessage((void *)0,ZT_SOFTWARE_UPDATE_SERVICE,ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE,gd.data(),gd.size()); + //printf(">> GET_DATA @%u\n",(unsigned int)_download.length()); + } + } + + return false; +} + +void SoftwareUpdater::apply() +{ + std::string updatePath(_homePath + ZT_PATH_SEPARATOR_S ZT_SOFTWARE_UPDATE_BIN_FILENAME); + if ((_latestMeta.is_object())&&(_latestValid)&&(OSUtils::fileExists(updatePath.c_str(),false))) { +#ifdef __WINDOWS__ + std::string cmdArgs(OSUtils::jsonString(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_EXEC_ARGS],"")); + if (cmdArgs.length() > 0) { + updatePath.push_back(' '); + updatePath.append(cmdArgs); + } + STARTUPINFOA si; + PROCESS_INFORMATION pi; + memset(&si,0,sizeof(si)); + memset(&pi,0,sizeof(pi)); + CreateProcessA(NULL,const_cast(updatePath.c_str()),NULL,NULL,FALSE,CREATE_NO_WINDOW|CREATE_NEW_PROCESS_GROUP,NULL,NULL,&si,&pi); + // Windows doesn't exit here -- updater will stop the service during update, etc. -- but we do want to stop multiple runs from happening + _latestMeta = nlohmann::json(); + _latestValid = false; +#else + char *argv[256]; + unsigned long ac = 0; + argv[ac++] = const_cast(updatePath.c_str()); + const std::vector argsSplit(OSUtils::split(OSUtils::jsonString(_latestMeta[ZT_SOFTWARE_UPDATE_JSON_UPDATE_EXEC_ARGS],"").c_str()," ","\\","\"")); + for(std::vector::const_iterator a(argsSplit.begin());a!=argsSplit.end();++a) { + argv[ac] = const_cast(a->c_str()); + if (++ac == 255) break; + } + argv[ac] = (char *)0; + chmod(updatePath.c_str(),0700); + + // Close all open file descriptors except stdout/stderr/etc. + int minMyFd = STDIN_FILENO; + if (STDOUT_FILENO > minMyFd) minMyFd = STDOUT_FILENO; + if (STDERR_FILENO > minMyFd) minMyFd = STDERR_FILENO; + ++minMyFd; +#ifdef _SC_OPEN_MAX + int maxMyFd = (int)sysconf(_SC_OPEN_MAX); + if (maxMyFd <= minMyFd) + maxMyFd = 65536; +#else + int maxMyFd = 65536; +#endif + while (minMyFd < maxMyFd) + close(minMyFd++); + + execv(updatePath.c_str(),argv); + fprintf(stderr,"FATAL: unable to execute software update binary at %s\n",updatePath.c_str()); + exit(1); +#endif + } +} + +} // namespace ZeroTier diff --git a/service/SoftwareUpdater.hpp b/service/SoftwareUpdater.hpp new file mode 100644 index 0000000..4bb0ef5 --- /dev/null +++ b/service/SoftwareUpdater.hpp @@ -0,0 +1,209 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef ZT_SOFTWAREUPDATER_HPP +#define ZT_SOFTWAREUPDATER_HPP + +#include +#include + +#include +#include +#include + +#include "../include/ZeroTierOne.h" + +#include "../node/Identity.hpp" +#include "../node/Array.hpp" +#include "../node/Packet.hpp" + +#include "../ext/json/json.hpp" + +/** + * VERB_USER_MESSAGE type ID for software update messages + */ +#define ZT_SOFTWARE_UPDATE_USER_MESSAGE_TYPE 100 + +/** + * ZeroTier address of node that provides software updates + */ +#define ZT_SOFTWARE_UPDATE_SERVICE 0xb1d366e81fULL + +/** + * ZeroTier identity that must be used to sign software updates + * + * df24360f3e - update-signing-key-0010 generated Fri Jan 13th, 2017 at 4:05pm PST + */ +#define ZT_SOFTWARE_UPDATE_SIGNING_AUTHORITY "df24360f3e:0:06072642959c8dfb68312904d74d90197c8a7692697caa1b3fd769eca714f4370fab462fcee6ebcb5fffb63bc5af81f28a2514b2cd68daabb42f7352c06f21db" + +/** + * Chunk size for in-band downloads (can be changed, designed to always fit in one UDP packet easily) + */ +#define ZT_SOFTWARE_UPDATE_CHUNK_SIZE (ZT_PROTO_MAX_PACKET_LENGTH - 128) + +/** + * Sanity limit for the size of an update binary image + */ +#define ZT_SOFTWARE_UPDATE_MAX_SIZE (1024 * 1024 * 256) + +/** + * How often (ms) do we check? + */ +#define ZT_SOFTWARE_UPDATE_CHECK_PERIOD (60 * 10 * 1000) + +/** + * Default update channel + */ +#define ZT_SOFTWARE_UPDATE_DEFAULT_CHANNEL "release" + +/** + * Filename for latest update's binary image + */ +#define ZT_SOFTWARE_UPDATE_BIN_FILENAME "latest-update.exe" + +#define ZT_SOFTWARE_UPDATE_JSON_VERSION_MAJOR "vMajor" +#define ZT_SOFTWARE_UPDATE_JSON_VERSION_MINOR "vMinor" +#define ZT_SOFTWARE_UPDATE_JSON_VERSION_REVISION "vRev" +#define ZT_SOFTWARE_UPDATE_JSON_VERSION_BUILD "vBuild" +#define ZT_SOFTWARE_UPDATE_JSON_PLATFORM "platform" +#define ZT_SOFTWARE_UPDATE_JSON_ARCHITECTURE "arch" +#define ZT_SOFTWARE_UPDATE_JSON_VENDOR "vendor" +#define ZT_SOFTWARE_UPDATE_JSON_CHANNEL "channel" +#define ZT_SOFTWARE_UPDATE_JSON_EXPECT_SIGNED_BY "expectedSigner" +#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNED_BY "signer" +#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIGNATURE "signature" +#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_HASH "hash" +#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_SIZE "size" +#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_EXEC_ARGS "execArgs" +#define ZT_SOFTWARE_UPDATE_JSON_UPDATE_URL "url" + +namespace ZeroTier { + +class Node; + +/** + * This class handles retrieving and executing updates, or serving them + */ +class SoftwareUpdater +{ +public: + /** + * Each message begins with an 8-bit message verb + */ + enum MessageVerb + { + /** + * Payload: JSON containing current system platform, version, etc. + */ + VERB_GET_LATEST = 1, + + /** + * Payload: JSON describing latest update for this target. (No response is sent if there is none.) + */ + VERB_LATEST = 2, + + /** + * Payload: + * <[16] first 128 bits of hash of data object> + * <[4] 32-bit index of chunk to get> + */ + VERB_GET_DATA = 3, + + /** + * Payload: + * <[16] first 128 bits of hash of data object> + * <[4] 32-bit index of chunk> + * <[...] chunk data> + */ + VERB_DATA = 4 + }; + + SoftwareUpdater(Node &node,const std::string &homePath); + ~SoftwareUpdater(); + + /** + * Set whether or not we will distribute updates + * + * @param distribute If true, scan update-dist.d now and distribute updates found there -- if false, clear and stop distributing + */ + void setUpdateDistribution(bool distribute); + + /** + * Handle a software update user message + * + * @param origin ZeroTier address of message origin + * @param data Message payload + * @param len Length of message + */ + void handleSoftwareUpdateUserMessage(uint64_t origin,const void *data,unsigned int len); + + /** + * Check for updates and do other update-related housekeeping + * + * It should be called about every 10 seconds. + * + * @return True if we've downloaded and verified an update + */ + bool check(const uint64_t now); + + /** + * @return Meta-data for downloaded update or NULL if none + */ + inline const nlohmann::json &pending() const { return _latestMeta; } + + /** + * Apply any ready update now + * + * Depending on the platform this function may never return and may forcibly + * exit the process. It does nothing if no update is ready. + */ + void apply(); + + /** + * Set software update channel + * + * @param channel 'release', 'beta', etc. + */ + inline void setChannel(const std::string &channel) { _channel = channel; } + +private: + Node &_node; + uint64_t _lastCheckTime; + std::string _homePath; + std::string _channel; + FILE *_distLog; + + // Offered software updates if we are an update host (we have update-dist.d and update hosting is enabled) + struct _D + { + nlohmann::json meta; + std::string bin; + }; + std::map< Array,_D > _dist; // key is first 16 bytes of hash + + nlohmann::json _latestMeta; + bool _latestValid; + + std::string _download; + Array _downloadHashPrefix; + unsigned long _downloadLength; +}; + +} // namespace ZeroTier + +#endif diff --git a/tcp-proxy/Makefile b/tcp-proxy/Makefile new file mode 100644 index 0000000..af4e71e --- /dev/null +++ b/tcp-proxy/Makefile @@ -0,0 +1,7 @@ +CXX=$(shell which clang++ g++ c++ 2>/dev/null | head -n 1) + +all: + $(CXX) -O3 -fno-rtti -o tcp-proxy tcp-proxy.cpp + +clean: + rm -f *.o tcp-proxy *.dSYM diff --git a/tcp-proxy/README.md b/tcp-proxy/README.md new file mode 100644 index 0000000..6f347d6 --- /dev/null +++ b/tcp-proxy/README.md @@ -0,0 +1,4 @@ +TCP Proxy Server +====== + +This is the TCP proxy server we run for TCP tunneling from peers behind fascist NATs. Regular users won't have much use for this. diff --git a/tcp-proxy/tcp-proxy.cpp b/tcp-proxy/tcp-proxy.cpp new file mode 100644 index 0000000..a7906aa --- /dev/null +++ b/tcp-proxy/tcp-proxy.cpp @@ -0,0 +1,317 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +// HACK! Will eventually use epoll() or something in Phy<> instead of select(). +// Also be sure to change ulimit -n and fs.file-max in /etc/sysctl.conf on relays. +#if defined(__linux__) || defined(__LINUX__) || defined(__LINUX) || defined(LINUX) +#include +#include +#undef __FD_SETSIZE +#define __FD_SETSIZE 1048576 +#undef FD_SETSIZE +#define FD_SETSIZE 1048576 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../osdep/Phy.hpp" + +#define ZT_TCP_PROXY_CONNECTION_TIMEOUT_SECONDS 300 +#define ZT_TCP_PROXY_TCP_PORT 443 + +using namespace ZeroTier; + +/* + * ZeroTier TCP Proxy Server + * + * This implements a simple packet encapsulation that is designed to look like + * a TLS connection. It's not a TLS connection, but it sends TLS format record + * headers. It could be extended in the future to implement a fake TLS + * handshake. + * + * At the moment, each packet is just made to look like TLS application data: + * <[1] TLS content type> - currently 0x17 for "application data" + * <[1] TLS major version> - currently 0x03 for TLS 1.2 + * <[1] TLS minor version> - currently 0x03 for TLS 1.2 + * <[2] payload length> - 16-bit length of payload in bytes + * <[...] payload> - Message payload + * + * TCP is inherently inefficient for encapsulating Ethernet, since TCP and TCP + * like protocols over TCP lead to double-ACKs. So this transport is only used + * to enable access when UDP or other datagram protocols are not available. + * + * Clients send a greeting, which is a four-byte message that contains: + * <[1] ZeroTier major version> + * <[1] minor version> + * <[2] revision> + * + * If a client has sent a greeting, it uses the new version of this protocol + * in which every encapsulated ZT packet is prepended by an IP address where + * it should be forwarded (or where it came from for replies). This causes + * this proxy to act as a remote UDP socket similar to a socks proxy, which + * will allow us to move this function off the rootservers and onto dedicated + * proxy nodes. + * + * Older ZT clients that do not send this message get their packets relayed + * to/from 127.0.0.1:9993, which will allow them to talk to and relay via + * the ZT node on the same machine as the proxy. We'll only support this for + * as long as such nodes appear to be in the wild. + */ + +struct TcpProxyService; +struct TcpProxyService +{ + Phy *phy; + int udpPortCounter; + struct Client + { + char tcpReadBuf[131072]; + char tcpWriteBuf[131072]; + unsigned long tcpWritePtr; + unsigned long tcpReadPtr; + PhySocket *tcp; + PhySocket *udp; + time_t lastActivity; + bool newVersion; + }; + std::map< PhySocket *,Client > clients; + + PhySocket *getUnusedUdp(void *uptr) + { + for(int i=0;i<65535;++i) { + ++udpPortCounter; + if (udpPortCounter > 0xfffe) + udpPortCounter = 1024; + struct sockaddr_in laddr; + memset(&laddr,0,sizeof(struct sockaddr_in)); + laddr.sin_family = AF_INET; + laddr.sin_port = htons((uint16_t)udpPortCounter); + PhySocket *udp = phy->udpBind(reinterpret_cast(&laddr),uptr); + if (udp) + return udp; + } + return (PhySocket *)0; + } + + void phyOnDatagram(PhySocket *sock,void **uptr,const struct sockaddr *localAddr,const struct sockaddr *from,void *data,unsigned long len) + { + if (!*uptr) + return; + if ((from->sa_family == AF_INET)&&(len >= 16)&&(len < 2048)) { + Client &c = *((Client *)*uptr); + c.lastActivity = time((time_t *)0); + + unsigned long mlen = len; + if (c.newVersion) + mlen += 7; // new clients get IP info + + if ((c.tcpWritePtr + 5 + mlen) <= sizeof(c.tcpWriteBuf)) { + if (!c.tcpWritePtr) + phy->setNotifyWritable(c.tcp,true); + + c.tcpWriteBuf[c.tcpWritePtr++] = 0x17; // look like TLS data + c.tcpWriteBuf[c.tcpWritePtr++] = 0x03; // look like TLS 1.2 + c.tcpWriteBuf[c.tcpWritePtr++] = 0x03; // look like TLS 1.2 + + c.tcpWriteBuf[c.tcpWritePtr++] = (char)((mlen >> 8) & 0xff); + c.tcpWriteBuf[c.tcpWritePtr++] = (char)(mlen & 0xff); + + if (c.newVersion) { + c.tcpWriteBuf[c.tcpWritePtr++] = (char)4; // IPv4 + *((uint32_t *)(c.tcpWriteBuf + c.tcpWritePtr)) = ((const struct sockaddr_in *)from)->sin_addr.s_addr; + c.tcpWritePtr += 4; + *((uint16_t *)(c.tcpWriteBuf + c.tcpWritePtr)) = ((const struct sockaddr_in *)from)->sin_port; + c.tcpWritePtr += 2; + } + + for(unsigned long i=0;i %.16llx\n",inet_ntoa(reinterpret_cast(from)->sin_addr),(int)ntohs(reinterpret_cast(from)->sin_port),(unsigned long long)&c); + } + } + + void phyOnTcpConnect(PhySocket *sock,void **uptr,bool success) + { + // unused, we don't initiate outbound connections + } + + void phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from) + { + Client &c = clients[sockN]; + PhySocket *udp = getUnusedUdp((void *)&c); + if (!udp) { + phy->close(sockN); + clients.erase(sockN); + //printf("** TCP rejected, no more UDP ports to assign\n"); + return; + } + c.tcpWritePtr = 0; + c.tcpReadPtr = 0; + c.tcp = sockN; + c.udp = udp; + c.lastActivity = time((time_t *)0); + c.newVersion = false; + *uptrN = (void *)&c; + //printf("<< TCP from %s -> %.16llx\n",inet_ntoa(reinterpret_cast(from)->sin_addr),(unsigned long long)&c); + } + + void phyOnTcpClose(PhySocket *sock,void **uptr) + { + if (!*uptr) + return; + Client &c = *((Client *)*uptr); + phy->close(c.udp); + clients.erase(sock); + //printf("** TCP %.16llx closed\n",(unsigned long long)*uptr); + } + + void phyOnTcpData(PhySocket *sock,void **uptr,void *data,unsigned long len) + { + Client &c = *((Client *)*uptr); + c.lastActivity = time((time_t *)0); + + for(unsigned long i=0;i= sizeof(c.tcpReadBuf)) { + phy->close(sock); + return; + } + c.tcpReadBuf[c.tcpReadPtr++] = ((const char *)data)[i]; + + if (c.tcpReadPtr >= 5) { + unsigned long mlen = ( ((((unsigned long)c.tcpReadBuf[3]) & 0xff) << 8) | (((unsigned long)c.tcpReadBuf[4]) & 0xff) ); + if (c.tcpReadPtr >= (mlen + 5)) { + if (mlen == 4) { + // Right now just sending this means the client is 'new enough' for the IP header + c.newVersion = true; + //printf("<< TCP %.16llx HELLO\n",(unsigned long long)*uptr); + } else if (mlen >= 7) { + char *payload = c.tcpReadBuf + 5; + unsigned long payloadLen = mlen; + + struct sockaddr_in dest; + memset(&dest,0,sizeof(dest)); + if (c.newVersion) { + if (*payload == (char)4) { + // New clients tell us where their packets go. + ++payload; + dest.sin_family = AF_INET; + dest.sin_addr.s_addr = *((uint32_t *)payload); + payload += 4; + dest.sin_port = *((uint16_t *)payload); // will be in network byte order already + payload += 2; + payloadLen -= 7; + } + } else { + // For old clients we will just proxy everything to a local ZT instance. The + // fact that this will come from 127.0.0.1 will in turn prevent that instance + // from doing unite() with us. It'll just forward. There will not be many of + // these. + dest.sin_family = AF_INET; + dest.sin_addr.s_addr = htonl(0x7f000001); // 127.0.0.1 + dest.sin_port = htons(9993); + } + + // Note: we do not relay to privileged ports... just an abuse prevention rule. + if ((ntohs(dest.sin_port) > 1024)&&(payloadLen >= 16)) { + phy->udpSend(c.udp,(const struct sockaddr *)&dest,payload,payloadLen); + //printf(">> TCP %.16llx to %s:%d\n",(unsigned long long)*uptr,inet_ntoa(dest.sin_addr),(int)ntohs(dest.sin_port)); + } + } + + memmove(c.tcpReadBuf,c.tcpReadBuf + (mlen + 5),c.tcpReadPtr -= (mlen + 5)); + } + } + } + } + + void phyOnTcpWritable(PhySocket *sock,void **uptr) + { + Client &c = *((Client *)*uptr); + if (c.tcpWritePtr) { + long n = phy->streamSend(sock,c.tcpWriteBuf,c.tcpWritePtr); + if (n > 0) { + memmove(c.tcpWriteBuf,c.tcpWriteBuf + n,c.tcpWritePtr -= (unsigned long)n); + if (!c.tcpWritePtr) + phy->setNotifyWritable(sock,false); + } + } else phy->setNotifyWritable(sock,false); + } + + void doHousekeeping() + { + std::vector toClose; + time_t now = time((time_t *)0); + for(std::map< PhySocket *,Client >::iterator c(clients.begin());c!=clients.end();++c) { + if ((now - c->second.lastActivity) >= ZT_TCP_PROXY_CONNECTION_TIMEOUT_SECONDS) { + toClose.push_back(c->first); + toClose.push_back(c->second.udp); + } + } + for(std::vector::iterator s(toClose.begin());s!=toClose.end();++s) + phy->close(*s); + } +}; + +int main(int argc,char **argv) +{ + signal(SIGPIPE,SIG_IGN); + signal(SIGHUP,SIG_IGN); + srand(time((time_t *)0)); + + TcpProxyService svc; + Phy phy(&svc,false,true); + svc.phy = &phy; + svc.udpPortCounter = 1023; + + { + struct sockaddr_in laddr; + memset(&laddr,0,sizeof(laddr)); + laddr.sin_family = AF_INET; + laddr.sin_port = htons(ZT_TCP_PROXY_TCP_PORT); + if (!phy.tcpListen((const struct sockaddr *)&laddr)) { + fprintf(stderr,"%s: fatal error: unable to bind TCP port %d\n",argv[0],ZT_TCP_PROXY_TCP_PORT); + return 1; + } + } + + time_t lastDidHousekeeping = time((time_t *)0); + for(;;) { + phy.poll(120000); + time_t now = time((time_t *)0); + if ((now - lastDidHousekeeping) > 120) { + lastDidHousekeeping = now; + svc.doHousekeeping(); + } + } + + return 0; +} diff --git a/version.h b/version.h new file mode 100644 index 0000000..d20f614 --- /dev/null +++ b/version.h @@ -0,0 +1,53 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 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 . + */ + +#ifndef _ZT_VERSION_H +#define _ZT_VERSION_H + +/** + * Major version + */ +#define ZEROTIER_ONE_VERSION_MAJOR 1 + +/** + * Minor version + */ +#define ZEROTIER_ONE_VERSION_MINOR 2 + +/** + * Revision + */ +#define ZEROTIER_ONE_VERSION_REVISION 4 + +/** + * Build version + * + * This starts at 0 for each major.minor.rev tuple and can be incremented + * to force a minor update without an actual version number change. It's + * not part of the actual release version number. + */ +#define ZEROTIER_ONE_VERSION_BUILD 0 + +#ifndef ZT_BUILD_ARCHITECTURE +#define ZT_BUILD_ARCHITECTURE 0 +#endif +#ifndef ZT_BUILD_PLATFORM +#define ZT_BUILD_PLATFORM 0 +#endif + +#endif diff --git a/windows/README.md b/windows/README.md new file mode 100644 index 0000000..9f3f48b --- /dev/null +++ b/windows/README.md @@ -0,0 +1,3 @@ +This folder contains the Windows driver code, Windows-specific service code, and the Microsoft Visual Studio projects and "solution" for doing Windows builds. + +This code may also build with MinGW but this hasn't been tested. diff --git a/windows/TapDriver6/TapDriver6.vcxproj b/windows/TapDriver6/TapDriver6.vcxproj new file mode 100644 index 0000000..cf6b150 --- /dev/null +++ b/windows/TapDriver6/TapDriver6.vcxproj @@ -0,0 +1,396 @@ + + + + + Win8 Debug + Win32 + + + Win8 Release + Win32 + + + Win7 Debug + Win32 + + + Win7 Release + Win32 + + + Vista Debug + Win32 + + + Vista Release + Win32 + + + Win8 Debug + x64 + + + Win8 Release + x64 + + + Win7 Debug + x64 + + + Win7 Release + x64 + + + Vista Debug + x64 + + + Vista Release + x64 + + + + {43BA7584-D4DB-4F7C-90FC-E2B18A68A213} + {1bc93793-694f-48fe-9372-81e2b05556fd} + v4.5 + 11.0 + Win8 Debug + Win32 + + + TapDriver6 + $(VCTargetsPath11) + + + Driver + KMDF + + + + Windows8 + true + WindowsKernelModeDriver10.0 + + + Windows8 + false + WindowsKernelModeDriver10.0 + + + Windows7 + true + WindowsKernelModeDriver10.0 + + + Windows7 + false + WindowsKernelModeDriver10.0 + + + Vista + true + WindowsKernelModeDriver8.0 + + + Vista + false + 1 + 7 + WindowsKernelModeDriver8.0 + + + Windows8 + true + WindowsKernelModeDriver10.0 + + + Windows8 + false + WindowsKernelModeDriver10.0 + + + Windows7 + true + WindowsKernelModeDriver10.0 + + + Windows7 + false + WindowsKernelModeDriver10.0 + + + Vista + true + WindowsKernelModeDriver8.0 + + + Vista + false + 1 + 7 + WindowsKernelModeDriver8.0 + + + + + + + + + + zttap300 + + + DbgengKernelDebugger + + + zttap300 + + + zttap300 + + + zttap300 + + + zttap300 + + + zttap300 + + + zttap300 + + + zttap300 + + + zttap300 + + + zttap300 + + + zttap300 + + + zttap300 + + + + false + trace.h + false + false + false + false + false + false + false + false + false + false + false + false + false + Level1 + Level1 + Level1 + Level1 + Level1 + Level1 + Level1 + Level1 + Level1 + Level1 + Level1 + Level1 + Default + Default + Default + Default + Default + Default + Default + Default + Default + Default + Default + Default + + + C:\WinDDK\7600.16385.1\lib\win7\amd64\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\wdmsec.lib;%(AdditionalDependencies) + + + C:\WinDDK\7600.16385.1\lib\win7\amd64\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\wdmsec.lib;%(AdditionalDependencies) + + + C:\WinDDK\7600.16385.1\lib\win7\amd64\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\wdmsec.lib;%(AdditionalDependencies) + + + $(DDK_LIB_PATH)ndis.lib;$(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) + + + $(DDK_LIB_PATH)ndis.lib;$(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) + + + C:\WinDDK\7600.16385.1\lib\win7\amd64\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\amd64\wdmsec.lib;%(AdditionalDependencies) + + + C:\WinDDK\7600.16385.1\lib\win7\i386\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\wdmsec.lib;%(AdditionalDependencies) + + + C:\WinDDK\7600.16385.1\lib\win7\i386\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\wdmsec.lib;%(AdditionalDependencies) + + + C:\WinDDK\7600.16385.1\lib\win7\i386\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\wdmsec.lib;%(AdditionalDependencies) + + + $(DDK_LIB_PATH)ndis.lib;$(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) + + + $(DDK_LIB_PATH)ndis.lib;$(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) + + + C:\WinDDK\7600.16385.1\lib\win7\i386\ndis.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\ntstrsafe.lib;C:\WinDDK\7600.16385.1\lib\win7\i386\wdmsec.lib;%(AdditionalDependencies) + + + 3.00.00.0 + false + false + + + 3.00.00.0 + false + false + + + 3.00.00.0 + false + false + + + 3.00.00.0 + true + true + + + zttap300.cat + + + + + false + false + + + + 3.00.00.0 + false + false + + + 3.00.00.0 + false + false + + + 3.00.00.0 + false + false + + + 3.00.00.0 + false + false + + + 3.00.00.0 + true + true + + + zttap300.cat + -v "3.00.00.0" %(AdditionalOptions) + + + 3.00.00.0 + true + true + -v "3.00.00.0" %(AdditionalOptions) + + + 3.00.00.0 + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3.00.00.0 + false + 3.00.00.0 + false + 3.00.00.0 + false + 3.00.00.0 + false + 3.00.00.0 + false + 3.00.00.0 + false + 3.00.00.0 + false + 3.00.00.0 + false + 3.00.00.0 + false + 3.00.00.0 + false + 3.00.00.0 + false + false + 3.00.00.0 + + + + + + \ No newline at end of file diff --git a/windows/TapDriver6/TapDriver6.vcxproj.filters b/windows/TapDriver6/TapDriver6.vcxproj.filters new file mode 100644 index 0000000..14cbbde --- /dev/null +++ b/windows/TapDriver6/TapDriver6.vcxproj.filters @@ -0,0 +1,110 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {8E41214B-6785-4CFE-B992-037D68949A14} + inf;inv;inx;mof;mc; + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + Driver Files + + + \ No newline at end of file diff --git a/windows/TapDriver6/adapter.c b/windows/TapDriver6/adapter.c new file mode 100644 index 0000000..7ce4b31 --- /dev/null +++ b/windows/TapDriver6/adapter.c @@ -0,0 +1,1716 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// +// Include files. +// + +#include "tap.h" + +NDIS_OID TAPSupportedOids[] = +{ + OID_GEN_HARDWARE_STATUS, + OID_GEN_TRANSMIT_BUFFER_SPACE, + OID_GEN_RECEIVE_BUFFER_SPACE, + OID_GEN_TRANSMIT_BLOCK_SIZE, + OID_GEN_RECEIVE_BLOCK_SIZE, + OID_GEN_VENDOR_ID, + OID_GEN_VENDOR_DESCRIPTION, + OID_GEN_VENDOR_DRIVER_VERSION, + OID_GEN_CURRENT_PACKET_FILTER, + OID_GEN_CURRENT_LOOKAHEAD, + OID_GEN_DRIVER_VERSION, + OID_GEN_MAXIMUM_TOTAL_SIZE, + OID_GEN_XMIT_OK, + OID_GEN_RCV_OK, + OID_GEN_STATISTICS, +#ifdef IMPLEMENT_OPTIONAL_OIDS + OID_GEN_TRANSMIT_QUEUE_LENGTH, // Optional +#endif // IMPLEMENT_OPTIONAL_OIDS + OID_GEN_LINK_PARAMETERS, + OID_GEN_INTERRUPT_MODERATION, + OID_GEN_MEDIA_SUPPORTED, + OID_GEN_MEDIA_IN_USE, + OID_GEN_MAXIMUM_SEND_PACKETS, + OID_GEN_XMIT_ERROR, + OID_GEN_RCV_ERROR, + OID_GEN_RCV_NO_BUFFER, + OID_802_3_PERMANENT_ADDRESS, + OID_802_3_CURRENT_ADDRESS, + OID_802_3_MULTICAST_LIST, + OID_802_3_MAXIMUM_LIST_SIZE, + OID_802_3_RCV_ERROR_ALIGNMENT, + OID_802_3_XMIT_ONE_COLLISION, + OID_802_3_XMIT_MORE_COLLISIONS, +#ifdef IMPLEMENT_OPTIONAL_OIDS + OID_802_3_XMIT_DEFERRED, // Optional + OID_802_3_XMIT_MAX_COLLISIONS, // Optional + OID_802_3_RCV_OVERRUN, // Optional + OID_802_3_XMIT_UNDERRUN, // Optional + OID_802_3_XMIT_HEARTBEAT_FAILURE, // Optional + OID_802_3_XMIT_TIMES_CRS_LOST, // Optional + OID_802_3_XMIT_LATE_COLLISIONS, // Optional + OID_PNP_CAPABILITIES, // Optional +#endif // IMPLEMENT_OPTIONAL_OIDS +}; + +//====================================================================== +// TAP NDIS 6 Miniport Callbacks +//====================================================================== + +// Returns with reference count initialized to one. +PTAP_ADAPTER_CONTEXT +tapAdapterContextAllocate( + __in NDIS_HANDLE MiniportAdapterHandle +) +{ + PTAP_ADAPTER_CONTEXT adapter = NULL; + + adapter = (PTAP_ADAPTER_CONTEXT )NdisAllocateMemoryWithTagPriority( + GlobalData.NdisDriverHandle, + sizeof(TAP_ADAPTER_CONTEXT), + TAP_ADAPTER_TAG, + NormalPoolPriority + ); + + if(adapter) + { + NET_BUFFER_LIST_POOL_PARAMETERS nblPoolParameters = {0}; + + NdisZeroMemory(adapter,sizeof(TAP_ADAPTER_CONTEXT)); + + adapter->MiniportAdapterHandle = MiniportAdapterHandle; + + // Initialize cancel-safe IRP queue + tapIrpCsqInitialize(&adapter->PendingReadIrpQueue); + + // Initialize TAP send packet queue. + tapPacketQueueInitialize(&adapter->SendPacketQueue); + + // Allocate the adapter lock. + NdisAllocateSpinLock(&adapter->AdapterLock); + + // NBL pool for making TAP receive indications. + NdisZeroMemory(&nblPoolParameters, sizeof(NET_BUFFER_LIST_POOL_PARAMETERS)); + + // Initialize event used to determine when all receive NBLs have been returned. + NdisInitializeEvent(&adapter->ReceiveNblInFlightCountZeroEvent); + + nblPoolParameters.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + nblPoolParameters.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; + nblPoolParameters.Header.Size = NDIS_SIZEOF_NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; + nblPoolParameters.ProtocolId = NDIS_PROTOCOL_ID_DEFAULT; + nblPoolParameters.ContextSize = 0; + //nblPoolParameters.ContextSize = sizeof(RX_NETBUFLIST_RSVD); + nblPoolParameters.fAllocateNetBuffer = TRUE; + nblPoolParameters.PoolTag = TAP_RX_NBL_TAG; + +#pragma warning( suppress : 28197 ) + adapter->ReceiveNblPool = NdisAllocateNetBufferListPool( + adapter->MiniportAdapterHandle, + &nblPoolParameters); + + if (adapter->ReceiveNblPool == NULL) + { + DEBUGP (("[TAP] Couldn't allocate adapter receive NBL pool\n")); + NdisFreeMemory(adapter,0,0); + } + + // Add initial reference. Normally removed in AdapterHalt. + adapter->RefCount = 1; + + // Safe for multiple removes. + NdisInitializeListHead(&adapter->AdapterListLink); + + // + // The miniport adapter is initially powered up + // + adapter->CurrentPowerState = NdisDeviceStateD0; + } + + return adapter; +} + +VOID +tapReadPermanentAddress( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in NDIS_HANDLE ConfigurationHandle, + __out MACADDR PermanentAddress + ) +{ + NDIS_STATUS status; + NDIS_CONFIGURATION_PARAMETER *configParameter; + NDIS_STRING macKey = NDIS_STRING_CONST("MAC"); + ANSI_STRING macString; + BOOLEAN macFromRegistry = FALSE; + + // Read MAC parameter from registry. + NdisReadConfiguration( + &status, + &configParameter, + ConfigurationHandle, + &macKey, + NdisParameterString + ); + + if (status == NDIS_STATUS_SUCCESS) + { + if( (configParameter->ParameterType == NdisParameterString) + && (configParameter->ParameterData.StringData.Length >= 12) + ) + { + if (RtlUnicodeStringToAnsiString( + &macString, + &configParameter->ParameterData.StringData, + TRUE) == STATUS_SUCCESS + ) + { + macFromRegistry = ParseMAC (PermanentAddress, macString.Buffer); + RtlFreeAnsiString (&macString); + } + } + } + + if(!macFromRegistry) + { + // + // There is no (valid) address stashed in the registry parameter. + // + // Make up a dummy mac address based on the ANSI representation of the + // NetCfgInstanceId GUID. + // + GenerateRandomMac(PermanentAddress, MINIPORT_INSTANCE_ID(Adapter)); + } +} + +NDIS_STATUS +tapReadConfiguration( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + NDIS_CONFIGURATION_OBJECT configObject; + NDIS_HANDLE configHandle; + + DEBUGP (("[TAP] --> tapReadConfiguration\n")); + + // + // Setup defaults in case configuration cannot be opened. + // + Adapter->MtuSize = ETHERNET_MTU; + Adapter->MediaStateAlwaysConnected = FALSE; + Adapter->LogicalMediaState = FALSE; + Adapter->AllowNonAdmin = FALSE; + // + // Open the registry for this adapter to read advanced + // configuration parameters stored by the INF file. + // + NdisZeroMemory(&configObject, sizeof(configObject)); + + {C_ASSERT(sizeof(configObject) >= NDIS_SIZEOF_CONFIGURATION_OBJECT_REVISION_1);} + configObject.Header.Type = NDIS_OBJECT_TYPE_CONFIGURATION_OBJECT; + configObject.Header.Size = NDIS_SIZEOF_CONFIGURATION_OBJECT_REVISION_1; + configObject.Header.Revision = NDIS_CONFIGURATION_OBJECT_REVISION_1; + + configObject.NdisHandle = Adapter->MiniportAdapterHandle; + configObject.Flags = 0; + + status = NdisOpenConfigurationEx( + &configObject, + &configHandle + ); + + // Read on the opened configuration handle. + if(status == NDIS_STATUS_SUCCESS) + { + NDIS_CONFIGURATION_PARAMETER *configParameter; + NDIS_STRING mkey = NDIS_STRING_CONST("NetCfgInstanceId"); + + // + // Read NetCfgInstanceId from the registry. + // ------------------------------------ + // NetCfgInstanceId is required to create device and associated + // symbolic link for the adapter device. + // + // NetCfgInstanceId is a GUID string provided by NDIS that identifies + // the adapter instance. An example is: + // + // NetCfgInstanceId={410EB49D-2381-4FE7-9B36-498E22619DF0} + // + // Other names are derived from NetCfgInstanceId. For example, MiniportName: + // + // MiniportName=\DEVICE\{410EB49D-2381-4FE7-9B36-498E22619DF0} + // + NdisReadConfiguration ( + &status, + &configParameter, + configHandle, + &mkey, + NdisParameterString + ); + + if (status == NDIS_STATUS_SUCCESS) + { + if (configParameter->ParameterType == NdisParameterString) + { + DEBUGP (("[TAP] NdisReadConfiguration (NetCfgInstanceId=%wZ)\n", + &configParameter->ParameterData.StringData )); + + // Save NetCfgInstanceId as UNICODE_STRING. + Adapter->NetCfgInstanceId.Length = Adapter->NetCfgInstanceId.MaximumLength + = configParameter->ParameterData.StringData.Length; + + Adapter->NetCfgInstanceId.Buffer = Adapter->NetCfgInstanceIdBuffer; + + NdisMoveMemory( + Adapter->NetCfgInstanceId.Buffer, + configParameter->ParameterData.StringData.Buffer, + Adapter->NetCfgInstanceId.Length + ); + + // Save NetCfgInstanceId as ANSI_STRING as well. + if (RtlUnicodeStringToAnsiString ( + &Adapter->NetCfgInstanceIdAnsi, + &configParameter->ParameterData.StringData, + TRUE) != STATUS_SUCCESS + ) + { + DEBUGP (("[TAP] NetCfgInstanceId ANSI name conversion failed\n")); + status = NDIS_STATUS_RESOURCES; + } + } + else + { + DEBUGP (("[TAP] NetCfgInstanceId has invalid type\n")); + status = NDIS_STATUS_INVALID_DATA; + } + } + else + { + DEBUGP (("[TAP] NetCfgInstanceId failed\n")); + status = NDIS_STATUS_INVALID_DATA; + } + + if (status == NDIS_STATUS_SUCCESS) + { + NDIS_STATUS localStatus; // Use default if these fail. + NDIS_CONFIGURATION_PARAMETER *configParameter; + NDIS_STRING mtuKey = NDIS_STRING_CONST("MTU"); + NDIS_STRING mediaStatusKey = NDIS_STRING_CONST("MediaStatus"); +#if ENABLE_NONADMIN + NDIS_STRING allowNonAdminKey = NDIS_STRING_CONST("AllowNonAdmin"); +#endif + + // Read MTU from the registry. + NdisReadConfiguration ( + &localStatus, + &configParameter, + configHandle, + &mtuKey, + NdisParameterInteger + ); + + if (localStatus == NDIS_STATUS_SUCCESS) + { + if (configParameter->ParameterType == NdisParameterInteger) + { + int mtu = configParameter->ParameterData.IntegerData; + + if(mtu == 0) + { + mtu = ETHERNET_MTU; + } + + // Sanity check + if (mtu < MINIMUM_MTU) + { + mtu = MINIMUM_MTU; + } + else if (mtu > MAXIMUM_MTU) + { + mtu = MAXIMUM_MTU; + } + + Adapter->MtuSize = mtu; + } + } + + DEBUGP (("[%s] Using MTU %d\n", + MINIPORT_INSTANCE_ID (Adapter), + Adapter->MtuSize + )); + + // Read MediaStatus setting from registry. + NdisReadConfiguration ( + &localStatus, + &configParameter, + configHandle, + &mediaStatusKey, + NdisParameterInteger + ); + + if (localStatus == NDIS_STATUS_SUCCESS) + { + if (configParameter->ParameterType == NdisParameterInteger) + { + if(configParameter->ParameterData.IntegerData == 0) + { + // Connect state is appplication controlled. + DEBUGP(("[%s] Initial MediaConnectState: Application Controlled\n", + MINIPORT_INSTANCE_ID (Adapter))); + + Adapter->MediaStateAlwaysConnected = FALSE; + Adapter->LogicalMediaState = FALSE; + } + else + { + // Connect state is always connected. + DEBUGP(("[%s] Initial MediaConnectState: Always Connected\n", + MINIPORT_INSTANCE_ID (Adapter))); + + Adapter->MediaStateAlwaysConnected = TRUE; + Adapter->LogicalMediaState = TRUE; + } + } + } + + // Read MAC PermanentAddress setting from registry. + tapReadPermanentAddress( + Adapter, + configHandle, + Adapter->PermanentAddress + ); + + DEBUGP (("[%s] Using MAC PermanentAddress %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n", + MINIPORT_INSTANCE_ID (Adapter), + Adapter->PermanentAddress[0], + Adapter->PermanentAddress[1], + Adapter->PermanentAddress[2], + Adapter->PermanentAddress[3], + Adapter->PermanentAddress[4], + Adapter->PermanentAddress[5]) + ); + + // Now seed the current MAC address with the permanent address. + ETH_COPY_NETWORK_ADDRESS(Adapter->CurrentAddress, Adapter->PermanentAddress); + + DEBUGP (("[%s] Using MAC CurrentAddress %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n", + MINIPORT_INSTANCE_ID (Adapter), + Adapter->CurrentAddress[0], + Adapter->CurrentAddress[1], + Adapter->CurrentAddress[2], + Adapter->CurrentAddress[3], + Adapter->CurrentAddress[4], + Adapter->CurrentAddress[5]) + ); + + // Read optional AllowNonAdmin setting from registry. +#if ENABLE_NONADMIN + NdisReadConfiguration ( + &localStatus, + &configParameter, + configHandle, + &allowNonAdminKey, + NdisParameterInteger + ); + + if (localStatus == NDIS_STATUS_SUCCESS) + { + if (configParameter->ParameterType == NdisParameterInteger) + { + Adapter->AllowNonAdmin = TRUE; + } + } +#endif + } + + // Close the configuration handle. + NdisCloseConfiguration(configHandle); + } + else + { + DEBUGP (("[TAP] Couldn't open adapter registry\n")); + } + + DEBUGP (("[TAP] <-- tapReadConfiguration; status = %8.8X\n",status)); + + return status; +} + +VOID +tapAdapterContextAddToGlobalList( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + LOCK_STATE lockState; + PLIST_ENTRY listEntry = &Adapter->AdapterListLink; + + // Acquire global adapter list lock. + NdisAcquireReadWriteLock( + &GlobalData.Lock, + TRUE, // Acquire for write + &lockState + ); + + // Adapter context should NOT be in any list. + ASSERT( (listEntry->Flink == listEntry) && (listEntry->Blink == listEntry ) ); + + // Add reference to persist until after removal. + tapAdapterContextReference(Adapter); + + // Add the adapter context to the global list. + InsertTailList(&GlobalData.AdapterList,&Adapter->AdapterListLink); + + // Release global adapter list lock. + NdisReleaseReadWriteLock(&GlobalData.Lock,&lockState); +} + +VOID +tapAdapterContextRemoveFromGlobalList( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + LOCK_STATE lockState; + + // Acquire global adapter list lock. + NdisAcquireReadWriteLock( + &GlobalData.Lock, + TRUE, // Acquire for write + &lockState + ); + + // Remove the adapter context from the global list. + RemoveEntryList(&Adapter->AdapterListLink); + + // Safe for multiple removes. + NdisInitializeListHead(&Adapter->AdapterListLink); + + // Remove reference added in tapAdapterContextAddToGlobalList. + tapAdapterContextDereference(Adapter); + + // Release global adapter list lock. + NdisReleaseReadWriteLock(&GlobalData.Lock,&lockState); +} + +// Returns with added reference on adapter context. +PTAP_ADAPTER_CONTEXT +tapAdapterContextFromDeviceObject( + __in PDEVICE_OBJECT DeviceObject + ) +{ + LOCK_STATE lockState; + + // Acquire global adapter list lock. + NdisAcquireReadWriteLock( + &GlobalData.Lock, + FALSE, // Acquire for read + &lockState + ); + + if (!IsListEmpty(&GlobalData.AdapterList)) + { + PLIST_ENTRY entry = GlobalData.AdapterList.Flink; + PTAP_ADAPTER_CONTEXT adapter; + + while (entry != &GlobalData.AdapterList) + { + adapter = CONTAINING_RECORD(entry, TAP_ADAPTER_CONTEXT, AdapterListLink); + + // Match on DeviceObject + if(adapter->DeviceObject == DeviceObject ) + { + // Add reference to adapter context. + tapAdapterContextReference(adapter); + + // Release global adapter list lock. + NdisReleaseReadWriteLock(&GlobalData.Lock,&lockState); + + return adapter; + } + + // Move to next entry + entry = entry->Flink; + } + } + + // Release global adapter list lock. + NdisReleaseReadWriteLock(&GlobalData.Lock,&lockState); + + return (PTAP_ADAPTER_CONTEXT )NULL; +} + +NDIS_STATUS +AdapterSetOptions( + __in NDIS_HANDLE NdisDriverHandle, + __in NDIS_HANDLE DriverContext + ) +/*++ +Routine Description: + + The MiniportSetOptions function registers optional handlers. For each + optional handler that should be registered, this function makes a call + to NdisSetOptionalHandlers. + + MiniportSetOptions runs at IRQL = PASSIVE_LEVEL. + +Arguments: + + DriverContext The context handle + +Return Value: + + NDIS_STATUS_xxx code + +--*/ +{ + NDIS_STATUS status; + + DEBUGP (("[TAP] --> AdapterSetOptions\n")); + + // + // Set any optional handlers by filling out the appropriate struct and + // calling NdisSetOptionalHandlers here. + // + + status = NDIS_STATUS_SUCCESS; + + DEBUGP (("[TAP] <-- AdapterSetOptions; status = %8.8X\n",status)); + + return status; +} + +NDIS_STATUS +AdapterCreate( + __in NDIS_HANDLE MiniportAdapterHandle, + __in NDIS_HANDLE MiniportDriverContext, + __in PNDIS_MINIPORT_INIT_PARAMETERS MiniportInitParameters + ) +{ + PTAP_ADAPTER_CONTEXT adapter = NULL; + NDIS_STATUS status; + + UNREFERENCED_PARAMETER(MiniportDriverContext); + UNREFERENCED_PARAMETER(MiniportInitParameters); + + DEBUGP (("[TAP] --> AdapterCreate\n")); + + do + { + NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES regAttributes = {0}; + NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES genAttributes = {0}; + NDIS_PNP_CAPABILITIES pnpCapabilities = {0}; + + // + // Allocate adapter context structure and initialize all the + // memory resources for sending and receiving packets. + // + // Returns with reference count initialized to one. + // + adapter = tapAdapterContextAllocate(MiniportAdapterHandle); + + if(adapter == NULL) + { + DEBUGP (("[TAP] Couldn't allocate adapter memory\n")); + status = NDIS_STATUS_RESOURCES; + break; + } + + // Enter the Initializing state. + DEBUGP (("[TAP] Miniport State: Initializing\n")); + + tapAdapterAcquireLock(adapter,FALSE); + adapter->Locked.AdapterState = MiniportInitializingState; + tapAdapterReleaseLock(adapter,FALSE); + + // + // First read adapter configuration from registry. + // ----------------------------------------------- + // Subsequent device registration will fail if NetCfgInstanceId + // has not been successfully read. + // + status = tapReadConfiguration(adapter); + + // + // Set the registration attributes. + // + {C_ASSERT(sizeof(regAttributes) >= NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1);} + regAttributes.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES; + regAttributes.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1; + regAttributes.Header.Revision = NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1; + + regAttributes.MiniportAdapterContext = adapter; + regAttributes.AttributeFlags = TAP_ADAPTER_ATTRIBUTES_FLAGS; + + regAttributes.CheckForHangTimeInSeconds = TAP_ADAPTER_CHECK_FOR_HANG_TIME_IN_SECONDS; + regAttributes.InterfaceType = TAP_INTERFACE_TYPE; + + //NDIS_DECLARE_MINIPORT_ADAPTER_CONTEXT(TAP_ADAPTER_CONTEXT); + status = NdisMSetMiniportAttributes( + MiniportAdapterHandle, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)®Attributes + ); + + if (status != NDIS_STATUS_SUCCESS) + { + DEBUGP (("[TAP] NdisSetOptionalHandlers failed; Status 0x%08x\n",status)); + break; + } + + // + // Next, set the general attributes. + // + {C_ASSERT(sizeof(genAttributes) >= NDIS_SIZEOF_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_1);} + genAttributes.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES; + genAttributes.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_1; + genAttributes.Header.Revision = NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_1; + + // + // Specify the medium type that the NIC can support but not + // necessarily the medium type that the NIC currently uses. + // + genAttributes.MediaType = TAP_MEDIUM_TYPE; + + // + // Specifiy medium type that the NIC currently uses. + // + genAttributes.PhysicalMediumType = TAP_PHYSICAL_MEDIUM; + + // + // Specifiy the maximum network frame size, in bytes, that the NIC + // supports excluding the header. + // + genAttributes.MtuSize = TAP_FRAME_MAX_DATA_SIZE; + genAttributes.MaxXmitLinkSpeed = TAP_XMIT_SPEED; + genAttributes.XmitLinkSpeed = TAP_XMIT_SPEED; + genAttributes.MaxRcvLinkSpeed = TAP_RECV_SPEED; + genAttributes.RcvLinkSpeed = TAP_RECV_SPEED; + + if(adapter->MediaStateAlwaysConnected) + { + DEBUGP(("[%s] Initial MediaConnectState: Connected\n", + MINIPORT_INSTANCE_ID (adapter))); + + genAttributes.MediaConnectState = MediaConnectStateConnected; + } + else + { + DEBUGP(("[%s] Initial MediaConnectState: Disconnected\n", + MINIPORT_INSTANCE_ID (adapter))); + + genAttributes.MediaConnectState = MediaConnectStateDisconnected; + } + + genAttributes.MediaDuplexState = MediaDuplexStateFull; + + // + // The maximum number of bytes the NIC can provide as lookahead data. + // If that value is different from the size of the lookahead buffer + // supported by bound protocols, NDIS will call MiniportOidRequest to + // set the size of the lookahead buffer provided by the miniport driver + // to the minimum of the miniport driver and protocol(s) values. If the + // driver always indicates up full packets with + // NdisMIndicateReceiveNetBufferLists, it should set this value to the + // maximum total frame size, which excludes the header. + // + // Upper-layer drivers examine lookahead data to determine whether a + // packet that is associated with the lookahead data is intended for + // one or more of their clients. If the underlying driver supports + // multipacket receive indications, bound protocols are given full net + // packets on every indication. Consequently, this value is identical + // to that returned for OID_GEN_RECEIVE_BLOCK_SIZE. + // + genAttributes.LookaheadSize = TAP_MAX_LOOKAHEAD; + genAttributes.MacOptions = TAP_MAC_OPTIONS; + genAttributes.SupportedPacketFilters = TAP_SUPPORTED_FILTERS; + + // + // The maximum number of multicast addresses the NIC driver can manage. + // This list is global for all protocols bound to (or above) the NIC. + // Consequently, a protocol can receive NDIS_STATUS_MULTICAST_FULL from + // the NIC driver when attempting to set the multicast address list, + // even if the number of elements in the given list is less than the + // number originally returned for this query. + // + genAttributes.MaxMulticastListSize = TAP_MAX_MCAST_LIST; + genAttributes.MacAddressLength = MACADDR_SIZE; + + // + // Return the MAC address of the NIC burnt in the hardware. + // + ETH_COPY_NETWORK_ADDRESS(genAttributes.PermanentMacAddress, adapter->PermanentAddress); + + // + // Return the MAC address the NIC is currently programmed to use. Note + // that this address could be different from the permananent address as + // the user can override using registry. Read NdisReadNetworkAddress + // doc for more info. + // + ETH_COPY_NETWORK_ADDRESS(genAttributes.CurrentMacAddress, adapter->CurrentAddress); + + genAttributes.RecvScaleCapabilities = NULL; + genAttributes.AccessType = TAP_ACCESS_TYPE; + genAttributes.DirectionType = TAP_DIRECTION_TYPE; + genAttributes.ConnectionType = TAP_CONNECTION_TYPE; + genAttributes.IfType = TAP_IFTYPE; + genAttributes.IfConnectorPresent = TAP_HAS_PHYSICAL_CONNECTOR; + genAttributes.SupportedStatistics = TAP_SUPPORTED_STATISTICS; + genAttributes.SupportedPauseFunctions = NdisPauseFunctionsUnsupported; // IEEE 802.3 pause frames + genAttributes.DataBackFillSize = 0; + genAttributes.ContextBackFillSize = 0; + + // + // The SupportedOidList is an array of OIDs for objects that the + // underlying driver or its NIC supports. Objects include general, + // media-specific, and implementation-specific objects. NDIS forwards a + // subset of the returned list to protocols that make this query. That + // is, NDIS filters any supported statistics OIDs out of the list + // because protocols never make statistics queries. + // + genAttributes.SupportedOidList = TAPSupportedOids; + genAttributes.SupportedOidListLength = sizeof(TAPSupportedOids); + genAttributes.AutoNegotiationFlags = NDIS_LINK_STATE_DUPLEX_AUTO_NEGOTIATED; + + // + // Set power management capabilities + // + NdisZeroMemory(&pnpCapabilities, sizeof(pnpCapabilities)); + pnpCapabilities.WakeUpCapabilities.MinMagicPacketWakeUp = NdisDeviceStateUnspecified; + pnpCapabilities.WakeUpCapabilities.MinPatternWakeUp = NdisDeviceStateUnspecified; + genAttributes.PowerManagementCapabilities = &pnpCapabilities; + + status = NdisMSetMiniportAttributes( + MiniportAdapterHandle, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&genAttributes + ); + + if (status != NDIS_STATUS_SUCCESS) + { + DEBUGP (("[TAP] NdisMSetMiniportAttributes failed; Status 0x%08x\n",status)); + break; + } + + // + // Create the Win32 device I/O interface. + // + status = CreateTapDevice(adapter); + + if (status == NDIS_STATUS_SUCCESS) + { + // Add this adapter to the global adapter list. + tapAdapterContextAddToGlobalList(adapter); + } + else + { + DEBUGP (("[TAP] CreateTapDevice failed; Status 0x%08x\n",status)); + break; + } + } while(FALSE); + + if(status == NDIS_STATUS_SUCCESS) + { + // Enter the Paused state if initialization is complete. + DEBUGP (("[TAP] Miniport State: Paused\n")); + + tapAdapterAcquireLock(adapter,FALSE); + adapter->Locked.AdapterState = MiniportPausedState; + tapAdapterReleaseLock(adapter,FALSE); + } + else + { + if(adapter != NULL) + { + DEBUGP (("[TAP] Miniport State: Halted\n")); + + // + // Remove reference when adapter context was allocated + // --------------------------------------------------- + // This should result in freeing adapter context memory + // and assiciated resources. + // + tapAdapterContextDereference(adapter); + adapter = NULL; + } + } + + DEBUGP (("[TAP] <-- AdapterCreate; status = %8.8X\n",status)); + + return status; +} + +VOID +AdapterHalt( + __in NDIS_HANDLE MiniportAdapterContext, + __in NDIS_HALT_ACTION HaltAction + ) +/*++ + +Routine Description: + + Halt handler is called when NDIS receives IRP_MN_STOP_DEVICE, + IRP_MN_SUPRISE_REMOVE or IRP_MN_REMOVE_DEVICE requests from the PNP + manager. Here, the driver should free all the resources acquired in + MiniportInitialize and stop access to the hardware. NDIS will not submit + any further request once this handler is invoked. + + 1) Free and unmap all I/O resources. + 2) Disable interrupt and deregister interrupt handler. + 3) Deregister shutdown handler regsitered by + NdisMRegisterAdapterShutdownHandler . + 4) Cancel all queued up timer callbacks. + 5) Finally wait indefinitely for all the outstanding receive + packets indicated to the protocol to return. + + MiniportHalt runs at IRQL = PASSIVE_LEVEL. + + +Arguments: + + MiniportAdapterContext Pointer to the Adapter + HaltAction The reason for halting the adapter + +Return Value: + + None. + +--*/ +{ + PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; + + UNREFERENCED_PARAMETER(HaltAction); + + DEBUGP (("[TAP] --> AdapterHalt\n")); + + // Enter the Halted state. + DEBUGP (("[TAP] Miniport State: Halted\n")); + + tapAdapterAcquireLock(adapter,FALSE); + adapter->Locked.AdapterState = MiniportHaltedState; + tapAdapterReleaseLock(adapter,FALSE); + + // Remove this adapter from the global adapter list. + tapAdapterContextRemoveFromGlobalList(adapter); + + // BUGBUG!!! Call AdapterShutdownEx to do some of the work of stopping. + + // TODO!!! More... + + // + // Destroy the TAP Win32 device. + // + DestroyTapDevice(adapter); + + // + // Remove initial reference added in AdapterCreate. + // ------------------------------------------------ + // This should result in freeing adapter context memory + // and resources allocated in AdapterCreate. + // + tapAdapterContextDereference(adapter); + adapter = NULL; + + DEBUGP (("[TAP] <-- AdapterHalt\n")); +} + +VOID +tapWaitForReceiveNblInFlightCountZeroEvent( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + LONG nblCount; + + // + // Wait until higher-level protocol has returned all NBLs + // to the driver. + // + + // Add one NBL "bias" to insure allow event to be reset safely. + nblCount = NdisInterlockedIncrement(&Adapter->ReceiveNblInFlightCount); + ASSERT(nblCount > 0 ); + NdisResetEvent(&Adapter->ReceiveNblInFlightCountZeroEvent); + + // + // Now remove the bias and wait for the ReceiveNblInFlightCountZeroEvent + // if the count returned is not zero. + // + nblCount = NdisInterlockedDecrement(&Adapter->ReceiveNblInFlightCount); + ASSERT(nblCount >= 0); + + if(nblCount) + { + LARGE_INTEGER startTime, currentTime; + + NdisGetSystemUpTimeEx(&startTime); + + for (;;) + { + BOOLEAN waitResult = NdisWaitEvent( + &Adapter->ReceiveNblInFlightCountZeroEvent, + TAP_WAIT_POLL_LOOP_TIMEOUT + ); + + NdisGetSystemUpTimeEx(¤tTime); + + if (waitResult) + { + break; + } + + DEBUGP (("[%s] Waiting for %d in-flight receive NBLs to be returned.\n", + MINIPORT_INSTANCE_ID (Adapter), + Adapter->ReceiveNblInFlightCount + )); + } + + DEBUGP (("[%s] Waited %d ms for all in-flight NBLs to be returned.\n", + MINIPORT_INSTANCE_ID (Adapter), + (currentTime.LowPart - startTime.LowPart) + )); + } +} + +NDIS_STATUS +AdapterPause( + __in NDIS_HANDLE MiniportAdapterContext, + __in PNDIS_MINIPORT_PAUSE_PARAMETERS PauseParameters + ) +/*++ + +Routine Description: + + When a miniport receives a pause request, it enters into a Pausing state. + The miniport should not indicate up any more network data. Any pending + send requests must be completed, and new requests must be rejected with + NDIS_STATUS_PAUSED. + + Once all sends have been completed and all recieve NBLs have returned to + the miniport, the miniport enters the Paused state. + + While paused, the miniport can still service interrupts from the hardware + (to, for example, continue to indicate NDIS_STATUS_MEDIA_CONNECT + notifications). + + The miniport must continue to be able to handle status indications and OID + requests. MiniportPause is different from MiniportHalt because, in + general, the MiniportPause operation won't release any resources. + MiniportPause must not attempt to acquire any resources where allocation + can fail, since MiniportPause itself must not fail. + + + MiniportPause runs at IRQL = PASSIVE_LEVEL. + +Arguments: + + MiniportAdapterContext Pointer to the Adapter + MiniportPauseParameters Additional information about the pause operation + +Return Value: + + If the miniport is able to immediately enter the Paused state, it should + return NDIS_STATUS_SUCCESS. + + If the miniport must wait for send completions or pending receive NBLs, it + should return NDIS_STATUS_PENDING now, and call NDISMPauseComplete when the + miniport has entered the Paused state. + + No other return value is permitted. The pause operation must not fail. + +--*/ +{ + PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; + NDIS_STATUS status; + + UNREFERENCED_PARAMETER(PauseParameters); + + DEBUGP (("[TAP] --> AdapterPause\n")); + + // Enter the Pausing state. + DEBUGP (("[TAP] Miniport State: Pausing\n")); + + tapAdapterAcquireLock(adapter,FALSE); + adapter->Locked.AdapterState = MiniportPausingState; + tapAdapterReleaseLock(adapter,FALSE); + + // + // Stop the flow of network data through the receive path + // ------------------------------------------------------ + // In the Pausing and Paused state tapAdapterSendAndReceiveReady + // will prevent new calls to NdisMIndicateReceiveNetBufferLists + // to indicate additional receive NBLs to the host. + // + // However, there may be some in-flight NBLs owned by the driver + // that have been indicated to the host but have not yet been + // returned. + // + // Wait here for all in-flight receive indications to be returned. + // + tapWaitForReceiveNblInFlightCountZeroEvent(adapter); + + // + // Stop the flow of network data through the send path + // --------------------------------------------------- + // The initial implementation of the NDIS 6 send path follows the + // NDIS 5 pattern. Under this approach every send packet is copied + // into a driver-owned TAP_PACKET structure and the NBL owned by + // higher-level protocol is immediatly completed. + // + // With this deep-copy approach the driver never claims ownership + // of any send NBL. + // + // A future implementation may queue send NBLs and thereby eliminate + // the need for the unnecessary allocation and deep copy of each packet. + // + // So, nothing to do here for the send path for now... + + status = NDIS_STATUS_SUCCESS; + + // Enter the Paused state. + DEBUGP (("[TAP] Miniport State: Paused\n")); + + tapAdapterAcquireLock(adapter,FALSE); + adapter->Locked.AdapterState = MiniportPausedState; + tapAdapterReleaseLock(adapter,FALSE); + + DEBUGP (("[TAP] <-- AdapterPause; status = %8.8X\n",status)); + + return status; +} + +NDIS_STATUS +AdapterRestart( + __in NDIS_HANDLE MiniportAdapterContext, + __in PNDIS_MINIPORT_RESTART_PARAMETERS RestartParameters + ) +/*++ + +Routine Description: + + When a miniport receives a restart request, it enters into a Restarting + state. The miniport may begin indicating received data (e.g., using + NdisMIndicateReceiveNetBufferLists), handling status indications, and + processing OID requests in the Restarting state. However, no sends will be + requested while the miniport is in the Restarting state. + + Once the miniport is ready to send data, it has entered the Running state. + The miniport informs NDIS that it is in the Running state by returning + NDIS_STATUS_SUCCESS from this MiniportRestart function; or if this function + has already returned NDIS_STATUS_PENDING, by calling NdisMRestartComplete. + + + MiniportRestart runs at IRQL = PASSIVE_LEVEL. + +Arguments: + + MiniportAdapterContext Pointer to the Adapter + RestartParameters Additional information about the restart operation + +Return Value: + + If the miniport is able to immediately enter the Running state, it should + return NDIS_STATUS_SUCCESS. + + If the miniport is still in the Restarting state, it should return + NDIS_STATUS_PENDING now, and call NdisMRestartComplete when the miniport + has entered the Running state. + + Other NDIS_STATUS codes indicate errors. If an error is encountered, the + miniport must return to the Paused state (i.e., stop indicating receives). + +--*/ +{ + PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; + NDIS_STATUS status; + + UNREFERENCED_PARAMETER(RestartParameters); + + DEBUGP (("[TAP] --> AdapterRestart\n")); + + // Enter the Restarting state. + DEBUGP (("[TAP] Miniport State: Restarting\n")); + + tapAdapterAcquireLock(adapter,FALSE); + adapter->Locked.AdapterState = MiniportRestartingState; + tapAdapterReleaseLock(adapter,FALSE); + + status = NDIS_STATUS_SUCCESS; + + if(status == NDIS_STATUS_SUCCESS) + { + // Enter the Running state. + DEBUGP (("[TAP] Miniport State: Running\n")); + + tapAdapterAcquireLock(adapter,FALSE); + adapter->Locked.AdapterState = MiniportRunning; + tapAdapterReleaseLock(adapter,FALSE); + } + else + { + // Enter the Paused state if restart failed. + DEBUGP (("[TAP] Miniport State: Paused\n")); + + tapAdapterAcquireLock(adapter,FALSE); + adapter->Locked.AdapterState = MiniportPausedState; + tapAdapterReleaseLock(adapter,FALSE); + } + + DEBUGP (("[TAP] <-- AdapterRestart; status = %8.8X\n",status)); + + return status; +} + +BOOLEAN +tapAdapterReadAndWriteReady( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +/*++ + +Routine Description: + + This routine determines whether the adapter device interface can + accept read and write operations. + +Arguments: + + Adapter Pointer to our adapter context + +Return Value: + + Returns TRUE if the adapter state allows it to queue IRPs passed to + the device read and write callbacks. +--*/ +{ + if(!Adapter->TapDeviceCreated) + { + // TAP device not created or is being destroyed. + return FALSE; + } + + if(Adapter->TapFileObject == NULL) + { + // TAP application file object not open. + return FALSE; + } + + if(!Adapter->TapFileIsOpen) + { + // TAP application file object may be closing. + return FALSE; + } + + if(!Adapter->LogicalMediaState) + { + // Don't handle read/write if media not connected. + return FALSE; + } + + if(Adapter->CurrentPowerState != NdisDeviceStateD0) + { + // Don't handle read/write if device is not fully powered. + return FALSE; + } + + return TRUE; +} + +NDIS_STATUS +tapAdapterSendAndReceiveReady( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +/*++ + +Routine Description: + + This routine determines whether the adapter NDIS send and receive + paths are ready. + + This routine examines various adapter state variables and returns + a value that indicates whether the adapter NDIS interfaces can + accept send packets or indicate receive packets. + + In normal operation the adapter may temporarily enter and then exit + a not-ready condition. In particular, the adapter becomes not-ready + when in the Pausing/Paused states, but may become ready again when + Restarted. + + Runs at IRQL <= DISPATCH_LEVEL + +Arguments: + + Adapter Pointer to our adapter context + +Return Value: + + Returns NDIS_STATUS_SUCCESS if the adapter state allows it to + accept send packets and indicate receive packets. + + Otherwise it returns a NDIS_STATUS value other than NDIS_STATUS_SUCCESS. + These status values can be used directly as the completion status for + packets that must be completed immediatly in the send path. +--*/ +{ + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + + // + // Check various state variables to insure adapter is ready. + // + tapAdapterAcquireLock(Adapter,FALSE); + + if(!Adapter->LogicalMediaState) + { + status = NDIS_STATUS_MEDIA_DISCONNECTED; + } + else if(Adapter->CurrentPowerState != NdisDeviceStateD0) + { + status = NDIS_STATUS_LOW_POWER_STATE; + } + else if(Adapter->ResetInProgress) + { + status = NDIS_STATUS_RESET_IN_PROGRESS; + } + else + { + switch(Adapter->Locked.AdapterState) + { + case MiniportPausingState: + case MiniportPausedState: + status = NDIS_STATUS_PAUSED; + break; + + case MiniportHaltedState: + status = NDIS_STATUS_INVALID_STATE; + break; + + default: + status = NDIS_STATUS_SUCCESS; + break; + } + } + + tapAdapterReleaseLock(Adapter,FALSE); + + return status; +} + +BOOLEAN +AdapterCheckForHangEx( + __in NDIS_HANDLE MiniportAdapterContext + ) +/*++ + +Routine Description: + + The MiniportCheckForHangEx handler is called to report the state of the + NIC, or to monitor the responsiveness of an underlying device driver. + This is an optional function. If this handler is not specified, NDIS + judges the driver unresponsive when the driver holds + MiniportQueryInformation or MiniportSetInformation requests for a + time-out interval (deafult 4 sec), and then calls the driver's + MiniportReset function. A NIC driver's MiniportInitialize function can + extend NDIS's time-out interval by calling NdisMSetAttributesEx to + avoid unnecessary resets. + + MiniportCheckForHangEx runs at IRQL <= DISPATCH_LEVEL. + +Arguments: + + MiniportAdapterContext Pointer to our adapter + +Return Value: + + TRUE NDIS calls the driver's MiniportReset function. + FALSE Everything is fine + +--*/ +{ + PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; + + //DEBUGP (("[TAP] --> AdapterCheckForHangEx\n")); + + //DEBUGP (("[TAP] <-- AdapterCheckForHangEx; status = FALSE\n")); + + return FALSE; // Everything is fine +} + +NDIS_STATUS +AdapterReset( + __in NDIS_HANDLE MiniportAdapterContext, + __out PBOOLEAN AddressingReset + ) +/*++ + +Routine Description: + + MiniportResetEx is a required to issue a hardware reset to the NIC + and/or to reset the driver's software state. + + 1) The miniport driver can optionally complete any pending + OID requests. NDIS will submit no further OID requests + to the miniport driver for the NIC being reset until + the reset operation has finished. After the reset, + NDIS will resubmit to the miniport driver any OID requests + that were pending but not completed by the miniport driver + before the reset. + + 2) A deserialized miniport driver must complete any pending send + operations. NDIS will not requeue pending send packets for + a deserialized driver since NDIS does not maintain the send + queue for such a driver. + + 3) If MiniportReset returns NDIS_STATUS_PENDING, the driver must + complete the original request subsequently with a call to + NdisMResetComplete. + + MiniportReset runs at IRQL <= DISPATCH_LEVEL. + +Arguments: + +AddressingReset - If multicast or functional addressing information + or the lookahead size, is changed by a reset, + MiniportReset must set the variable at AddressingReset + to TRUE before it returns control. This causes NDIS to + call the MiniportSetInformation function to restore + the information. + +MiniportAdapterContext - Pointer to our adapter + +Return Value: + + NDIS_STATUS + +--*/ +{ + PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; + NDIS_STATUS status; + + UNREFERENCED_PARAMETER(MiniportAdapterContext); + UNREFERENCED_PARAMETER(AddressingReset); + + DEBUGP (("[TAP] --> AdapterReset\n")); + + // Indicate that adapter reset is in progress. + adapter->ResetInProgress = TRUE; + + // See note above... + *AddressingReset = FALSE; + + // BUGBUG!!! TODO!!! Lots of work here... + + // Indicate that adapter reset has completed. + adapter->ResetInProgress = FALSE; + + status = NDIS_STATUS_SUCCESS; + + DEBUGP (("[TAP] <-- AdapterReset; status = %8.8X\n",status)); + + return status; +} + +VOID +AdapterDevicePnpEventNotify( + __in NDIS_HANDLE MiniportAdapterContext, + __in PNET_DEVICE_PNP_EVENT NetDevicePnPEvent + ) +{ + PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; + + DEBUGP (("[TAP] --> AdapterDevicePnpEventNotify\n")); + +/* + switch (NetDevicePnPEvent->DevicePnPEvent) + { + case NdisDevicePnPEventSurpriseRemoved: + // + // Called when NDIS receives IRP_MN_SUPRISE_REMOVAL. + // NDIS calls MiniportHalt function after this call returns. + // + MP_SET_FLAG(Adapter, fMP_ADAPTER_SURPRISE_REMOVED); + DEBUGP(MP_INFO, "[%p] MPDevicePnpEventNotify: NdisDevicePnPEventSurpriseRemoved\n", Adapter); + break; + + case NdisDevicePnPEventPowerProfileChanged: + // + // After initializing a miniport driver and after miniport driver + // receives an OID_PNP_SET_POWER notification that specifies + // a device power state of NdisDeviceStateD0 (the powered-on state), + // NDIS calls the miniport's MiniportPnPEventNotify function with + // PnPEvent set to NdisDevicePnPEventPowerProfileChanged. + // + DEBUGP(MP_INFO, "[%p] MPDevicePnpEventNotify: NdisDevicePnPEventPowerProfileChanged\n", Adapter); + + if (NetDevicePnPEvent->InformationBufferLength == sizeof(ULONG)) + { + ULONG NdisPowerProfile = *((PULONG)NetDevicePnPEvent->InformationBuffer); + + if (NdisPowerProfile == NdisPowerProfileBattery) + { + DEBUGP(MP_INFO, "[%p] The host system is running on battery power\n", Adapter); + } + if (NdisPowerProfile == NdisPowerProfileAcOnLine) + { + DEBUGP(MP_INFO, "[%p] The host system is running on AC power\n", Adapter); + } + } + break; + + default: + DEBUGP(MP_ERROR, "[%p] MPDevicePnpEventNotify: unknown PnP event 0x%x\n", Adapter, NetDevicePnPEvent->DevicePnPEvent); + } +*/ + DEBUGP (("[TAP] <-- AdapterDevicePnpEventNotify\n")); +} + +VOID +AdapterShutdownEx( + __in NDIS_HANDLE MiniportAdapterContext, + __in NDIS_SHUTDOWN_ACTION ShutdownAction + ) +/*++ + +Routine Description: + + The MiniportShutdownEx handler restores hardware to its initial state when + the system is shut down, whether by the user or because an unrecoverable + system error occurred. This is to ensure that the NIC is in a known + state and ready to be reinitialized when the machine is rebooted after + a system shutdown occurs for any reason, including a crash dump. + + Here just disable the interrupt and stop the DMA engine. Do not free + memory resources or wait for any packet transfers to complete. Do not call + into NDIS at this time. + + This can be called at aribitrary IRQL, including in the context of a + bugcheck. + +Arguments: + + MiniportAdapterContext Pointer to our adapter + ShutdownAction The reason why NDIS called the shutdown function + +Return Value: + + None. + +--*/ +{ + PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; + + UNREFERENCED_PARAMETER(ShutdownAction); + UNREFERENCED_PARAMETER(MiniportAdapterContext); + + DEBUGP (("[TAP] --> AdapterShutdownEx\n")); + + // Enter the Shutdown state. + DEBUGP (("[TAP] Miniport State: Shutdown\n")); + + tapAdapterAcquireLock(adapter,FALSE); + adapter->Locked.AdapterState = MiniportShutdownState; + tapAdapterReleaseLock(adapter,FALSE); + + // + // BUGBUG!!! FlushIrpQueues??? + // + + DEBUGP (("[TAP] <-- AdapterShutdownEx\n")); +} + + +// Free adapter context memory and associated resources. +VOID +tapAdapterContextFree( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + PLIST_ENTRY listEntry = &Adapter->AdapterListLink; + + DEBUGP (("[TAP] --> tapAdapterContextFree\n")); + + // Adapter context should already be removed. + ASSERT( (listEntry->Flink == listEntry) && (listEntry->Blink == listEntry ) ); + + // Insure that adapter context has been removed from global adapter list. + RemoveEntryList(&Adapter->AdapterListLink); + + // Free the adapter lock. + NdisFreeSpinLock(&Adapter->AdapterLock); + + // Free the ANSI NetCfgInstanceId buffer. + if(Adapter->NetCfgInstanceIdAnsi.Buffer != NULL) + { + RtlFreeAnsiString(&Adapter->NetCfgInstanceIdAnsi); + } + + Adapter->NetCfgInstanceIdAnsi.Buffer = NULL; + + // Free the receive NBL pool. + if(Adapter->ReceiveNblPool != NULL ) + { + NdisFreeNetBufferListPool(Adapter->ReceiveNblPool); + } + + Adapter->ReceiveNblPool = NULL; + + NdisFreeMemory(Adapter,0,0); + + DEBUGP (("[TAP] <-- tapAdapterContextFree\n")); +} +ULONG +tapGetNetBufferFrameType( + __in PNET_BUFFER NetBuffer + ) +/*++ + +Routine Description: + + Reads the network frame's destination address to determine the type + (broadcast, multicast, etc) + + Runs at IRQL <= DISPATCH_LEVEL. + +Arguments: + + NetBuffer The NB to examine + +Return Value: + + NDIS_PACKET_TYPE_BROADCAST + NDIS_PACKET_TYPE_MULTICAST + NDIS_PACKET_TYPE_DIRECTED + +--*/ +{ + PETH_HEADER ethernetHeader; + + ethernetHeader = (PETH_HEADER )NdisGetDataBuffer( + NetBuffer, + sizeof(ETH_HEADER), + NULL, + 1, + 0 + ); + + ASSERT(ethernetHeader); + + if (ETH_IS_BROADCAST(ethernetHeader->dest)) + { + return NDIS_PACKET_TYPE_BROADCAST; + } + else if(ETH_IS_MULTICAST(ethernetHeader->dest)) + { + return NDIS_PACKET_TYPE_MULTICAST; + } + else + { + return NDIS_PACKET_TYPE_DIRECTED; + } + +} + +ULONG +tapGetNetBufferCountsFromNetBufferList( + __in PNET_BUFFER_LIST NetBufferList, + __inout_opt PULONG TotalByteCount // Of all linked NBs + ) +/*++ + +Routine Description: + + Returns the number of net buffers linked to the net buffer list. + + Optionally retuens the total byte count of all net buffers linked + to the net buffer list + + Runs at IRQL <= DISPATCH_LEVEL. + +Arguments: + + NetBufferList The NBL to examine + +Return Value: + + The number of net buffers linked to the net buffer list. + +--*/ +{ + ULONG netBufferCount = 0; + PNET_BUFFER currentNb; + + if(TotalByteCount) + { + *TotalByteCount = 0; + } + + currentNb = NET_BUFFER_LIST_FIRST_NB(NetBufferList); + + while(currentNb) + { + ++netBufferCount; + + if(TotalByteCount) + { + *TotalByteCount += NET_BUFFER_DATA_LENGTH(currentNb); + } + + // Move to next NB + currentNb = NET_BUFFER_NEXT_NB(currentNb); + } + + return netBufferCount; +} + +VOID +tapAdapterAcquireLock( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in BOOLEAN DispatchLevel + ) +{ + ASSERT(!DispatchLevel || (DISPATCH_LEVEL == KeGetCurrentIrql())); + + if (DispatchLevel) + { + NdisDprAcquireSpinLock(&Adapter->AdapterLock); + } + else + { + NdisAcquireSpinLock(&Adapter->AdapterLock); + } +} + +VOID +tapAdapterReleaseLock( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in BOOLEAN DispatchLevel + ) +{ + ASSERT(!DispatchLevel || (DISPATCH_LEVEL == KeGetCurrentIrql())); + + if (DispatchLevel) + { + NdisDprReleaseSpinLock(&Adapter->AdapterLock); + } + else + { + NdisReleaseSpinLock(&Adapter->AdapterLock); + } +} + + diff --git a/windows/TapDriver6/adapter.h b/windows/TapDriver6/adapter.h new file mode 100644 index 0000000..0ebaaea --- /dev/null +++ b/windows/TapDriver6/adapter.h @@ -0,0 +1,352 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __TAP_ADAPTER_CONTEXT_H_ +#define __TAP_ADAPTER_CONTEXT_H_ + +#include "tap.h" + +// Memory allocation tags. +#define TAP_ADAPTER_TAG ((ULONG)'ApaT') // "TapA +#define TAP_RX_NBL_TAG ((ULONG)'RpaT') // "TapR +#define TAP_RX_INJECT_BUFFER_TAG ((ULONG)'IpaT') // "TapI + +#define TAP_MAX_NDIS_NAME_LENGTH 64 // 38 character GUID string plus extra.. + +// TAP receive indication NBL flag definitions. +#define TAP_RX_NBL_FLAGS NBL_FLAGS_MINIPORT_RESERVED +#define TAP_RX_NBL_FLAGS_CLEAR_ALL(_NBL) ((_NBL)->Flags &= ~TAP_RX_NBL_FLAGS) +#define TAP_RX_NBL_FLAG_SET(_NBL, _F) ((_NBL)->Flags |= ((_F) & TAP_RX_NBL_FLAGS)) +#define TAP_RX_NBL_FLAG_CLEAR(_NBL, _F) ((_NBL)->Flags &= ~((_F) & TAP_RX_NBL_FLAGS)) +#define TAP_RX_NBL_FLAG_TEST(_NBL, _F) (((_NBL)->Flags & ((_F) & TAP_RX_NBL_FLAGS)) != 0) + +#define TAP_RX_NBL_FLAGS_IS_P2P 0x00001000 +#define TAP_RX_NBL_FLAGS_IS_INJECTED 0x00002000 + +// MSDN Ref: http://msdn.microsoft.com/en-us/library/windows/hardware/ff560490(v=vs.85).aspx +typedef +enum _TAP_MINIPORT_ADAPTER_STATE +{ + // The Halted state is the initial state of all adapters. When an + // adapter is in the Halted state, NDIS can call the driver's + // MiniportInitializeEx function to initialize the adapter. + MiniportHaltedState, + + // In the Shutdown state, a system shutdown and restart must occur + // before the system can use the adapter again. + MiniportShutdownState, + + // In the Initializing state, a miniport driver completes any + //operations that are required to initialize an adapter. + MiniportInitializingState, + + // Entering the Paused state... + MiniportPausingState, + + // In the Paused state, the adapter does not indicate received + // network data or accept send requests. + MiniportPausedState, + + // In the Running state, a miniport driver performs send and + // receive processing for an adapter. + MiniportRunning, + + // In the Restarting state, a miniport driver completes any + // operations that are required to restart send and receive + // operations for an adapter. + MiniportRestartingState +} TAP_MINIPORT_ADAPTER_STATE, *PTAP_MINIPORT_ADAPTER_STATE; + +// +// Each adapter managed by this driver has a TapAdapter struct. +// ------------------------------------------------------------ +// Since there is a one-to-one relationship between adapter instances +// and device instances this structure is the device extension as well. +// +typedef struct _TAP_ADAPTER_CONTEXT +{ + LIST_ENTRY AdapterListLink; + + volatile LONG RefCount; + + NDIS_HANDLE MiniportAdapterHandle; + + NDIS_SPIN_LOCK AdapterLock; // Lock for protection of state and outstanding sends and recvs + + // + // All fields that are protected by the AdapterLock are included + // in the Locked structure to remind us to take the Lock + // before accessing them :) + // + struct + { + TAP_MINIPORT_ADAPTER_STATE AdapterState; + } Locked; + + BOOLEAN ResetInProgress; + + // + // NetCfgInstanceId as UNICODE_STRING + // ---------------------------------- + // This a GUID string provided by NDIS that identifies the adapter instance. + // An example is: + // + // NetCfgInstanceId={410EB49D-2381-4FE7-9B36-498E22619DF0} + // + // Other names are derived from NetCfgInstanceId. For example, MiniportName: + // + // MiniportName=\DEVICE\{410EB49D-2381-4FE7-9B36-498E22619DF0} + // + NDIS_STRING NetCfgInstanceId; + WCHAR NetCfgInstanceIdBuffer[TAP_MAX_NDIS_NAME_LENGTH]; + +# define MINIPORT_INSTANCE_ID(a) ((a)->NetCfgInstanceIdAnsi.Buffer) + ANSI_STRING NetCfgInstanceIdAnsi; // Used occasionally + + ULONG MtuSize; // 1500 byte (typical) + + // TRUE if adapter should always be "connected" even when device node + // is not open by a userspace process. + // + // FALSE if connection state is application controlled. + BOOLEAN MediaStateAlwaysConnected; + + // TRUE if device is "connected". + BOOLEAN LogicalMediaState; + + NDIS_DEVICE_POWER_STATE CurrentPowerState; + + BOOLEAN AllowNonAdmin; + + MACADDR PermanentAddress; // From registry, if available + MACADDR CurrentAddress; + + // Device registration parameters from NdisRegisterDeviceEx. + NDIS_STRING DeviceName; + WCHAR DeviceNameBuffer[TAP_MAX_NDIS_NAME_LENGTH]; + + NDIS_STRING LinkName; + WCHAR LinkNameBuffer[TAP_MAX_NDIS_NAME_LENGTH]; + + NDIS_HANDLE DeviceHandle; + PDEVICE_OBJECT DeviceObject; + BOOLEAN TapDeviceCreated; // WAS: m_TapIsRunning + + PFILE_OBJECT TapFileObject; // Exclusive access + BOOLEAN TapFileIsOpen; // WAS: m_TapOpens + LONG TapFileOpenCount; // WAS: m_NumTapOpens + + // Cancel-Safe read IRP queue. + TAP_IRP_CSQ PendingReadIrpQueue; + + // Queue containing TAP packets representing host send NBs. These are + // waiting to be read by user-mode application. + TAP_PACKET_QUEUE SendPacketQueue; + + // NBL pool for making TAP receive indications. + NDIS_HANDLE ReceiveNblPool; + + volatile LONG ReceiveNblInFlightCount; +#define TAP_WAIT_POLL_LOOP_TIMEOUT 3000 // 3 seconds + NDIS_EVENT ReceiveNblInFlightCountZeroEvent; + + /* + // Info for point-to-point mode + BOOLEAN m_tun; + IPADDR m_localIP; + IPADDR m_remoteNetwork; + IPADDR m_remoteNetmask; + ETH_HEADER m_TapToUser; + ETH_HEADER m_UserToTap; + ETH_HEADER m_UserToTap_IPv6; // same as UserToTap but proto=ipv6 + */ + + // Info for DHCP server masquerade + /* + BOOLEAN m_dhcp_enabled; + IPADDR m_dhcp_addr; + ULONG m_dhcp_netmask; + IPADDR m_dhcp_server_ip; + BOOLEAN m_dhcp_server_arp; + MACADDR m_dhcp_server_mac; + ULONG m_dhcp_lease_time; + UCHAR m_dhcp_user_supplied_options_buffer[DHCP_USER_SUPPLIED_OPTIONS_BUFFER_SIZE]; + ULONG m_dhcp_user_supplied_options_buffer_len; + BOOLEAN m_dhcp_received_discover; + ULONG m_dhcp_bad_requests; + */ + + // Multicast list. Fixed size. + ULONG ulMCListSize; + UCHAR MCList[TAP_MAX_MCAST_LIST][MACADDR_SIZE]; + + ULONG PacketFilter; + ULONG ulLookahead; + + // + // Statistics + // ------------------------------------------------------------------------- + // + + // Packet counts + ULONG64 FramesRxDirected; + ULONG64 FramesRxMulticast; + ULONG64 FramesRxBroadcast; + ULONG64 FramesTxDirected; + ULONG64 FramesTxMulticast; + ULONG64 FramesTxBroadcast; + + // Byte counts + ULONG64 BytesRxDirected; + ULONG64 BytesRxMulticast; + ULONG64 BytesRxBroadcast; + ULONG64 BytesTxDirected; + ULONG64 BytesTxMulticast; + ULONG64 BytesTxBroadcast; + + // Count of transmit errors + ULONG TxAbortExcessCollisions; + ULONG TxLateCollisions; + ULONG TxDmaUnderrun; + ULONG TxLostCRS; + ULONG TxOKButDeferred; + ULONG OneRetry; + ULONG MoreThanOneRetry; + ULONG TotalRetries; + ULONG TransmitFailuresOther; + + // Count of receive errors + ULONG RxCrcErrors; + ULONG RxAlignmentErrors; + ULONG RxResourceErrors; + ULONG RxDmaOverrunErrors; + ULONG RxCdtFrames; + ULONG RxRuntErrors; + +#if PACKET_TRUNCATION_CHECK + LONG m_RxTrunc, m_TxTrunc; +#endif + + BOOLEAN m_InterfaceIsRunning; + LONG m_Rx, m_RxErr; + NDIS_MEDIUM m_Medium; + + // Help to tear down the adapter by keeping + // some state information on allocated + // resources. + BOOLEAN m_CalledAdapterFreeResources; + BOOLEAN m_RegisteredAdapterShutdownHandler; + +} TAP_ADAPTER_CONTEXT, *PTAP_ADAPTER_CONTEXT; + +FORCEINLINE +LONG +tapAdapterContextReference( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + LONG refCount = NdisInterlockedIncrement(&Adapter->RefCount); + + ASSERT(refCount>1); // Cannot dereference a zombie. + + return refCount; +} + +VOID +tapAdapterContextFree( + __in PTAP_ADAPTER_CONTEXT Adapter + ); + +FORCEINLINE +LONG +tapAdapterContextDereference( + IN PTAP_ADAPTER_CONTEXT Adapter + ) +{ + LONG refCount = NdisInterlockedDecrement(&Adapter->RefCount); + ASSERT(refCount >= 0); + if (!refCount) + { + tapAdapterContextFree(Adapter); + } + + return refCount; +} + +VOID +tapAdapterAcquireLock( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in BOOLEAN DispatchLevel + ); + +VOID +tapAdapterReleaseLock( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in BOOLEAN DispatchLevel + ); + +// Returns with added reference on adapter context. +PTAP_ADAPTER_CONTEXT +tapAdapterContextFromDeviceObject( + __in PDEVICE_OBJECT DeviceObject + ); + +BOOLEAN +tapAdapterReadAndWriteReady( + __in PTAP_ADAPTER_CONTEXT Adapter + ); + +NDIS_STATUS +tapAdapterSendAndReceiveReady( + __in PTAP_ADAPTER_CONTEXT Adapter + ); + +ULONG +tapGetNetBufferFrameType( + __in PNET_BUFFER NetBuffer + ); + +ULONG +tapGetNetBufferCountsFromNetBufferList( + __in PNET_BUFFER_LIST NetBufferList, + __inout_opt PULONG TotalByteCount // Of all linked NBs + ); + +// Prototypes for standard NDIS miniport entry points +MINIPORT_SET_OPTIONS AdapterSetOptions; +MINIPORT_INITIALIZE AdapterCreate; +MINIPORT_HALT AdapterHalt; +MINIPORT_UNLOAD TapDriverUnload; +MINIPORT_PAUSE AdapterPause; +MINIPORT_RESTART AdapterRestart; +MINIPORT_OID_REQUEST AdapterOidRequest; +MINIPORT_SEND_NET_BUFFER_LISTS AdapterSendNetBufferLists; +MINIPORT_RETURN_NET_BUFFER_LISTS AdapterReturnNetBufferLists; +MINIPORT_CANCEL_SEND AdapterCancelSend; +MINIPORT_CHECK_FOR_HANG AdapterCheckForHangEx; +MINIPORT_RESET AdapterReset; +MINIPORT_DEVICE_PNP_EVENT_NOTIFY AdapterDevicePnpEventNotify; +MINIPORT_SHUTDOWN AdapterShutdownEx; +MINIPORT_CANCEL_OID_REQUEST AdapterCancelOidRequest; + +#endif // __TAP_ADAPTER_CONTEXT_H_ \ No newline at end of file diff --git a/windows/TapDriver6/config.h b/windows/TapDriver6/config.h new file mode 100644 index 0000000..4d36c5a --- /dev/null +++ b/windows/TapDriver6/config.h @@ -0,0 +1,9 @@ +#define PRODUCT_NAME "ZeroTier One Virtual Port" +#define PRODUCT_VERSION "3.0.0" +#define PRODUCT_VERSION_RESOURCE 3,0,0,1 +#define PRODUCT_TAP_WIN_COMPONENT_ID "zttap300" +#define PRODUCT_TAP_WIN_MAJOR 3 +#define PRODUCT_TAP_WIN_MINOR 0 +#define PRODUCT_TAP_WIN_PROVIDER "ZeroTier Networks" +#define PRODUCT_TAP_WIN_DEVICE_DESCRIPTION PRODUCT_NAME +#define PRODUCT_TAP_WIN_RELDATE "04/25/2015" diff --git a/windows/TapDriver6/constants.h b/windows/TapDriver6/constants.h new file mode 100644 index 0000000..91a876f --- /dev/null +++ b/windows/TapDriver6/constants.h @@ -0,0 +1,196 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +//==================================================================== +// Product and Version public settings +//==================================================================== + +#define PRODUCT_STRING PRODUCT_TAP_DEVICE_DESCRIPTION + + +// +// Update the driver version number every time you release a new driver +// The high word is the major version. The low word is the minor version. +// Also make sure that VER_FILEVERSION specified in the .RC file also +// matches with the driver version because NDISTESTER checks for that. +// +#ifndef TAP_DRIVER_MAJOR_VERSION + +#define TAP_DRIVER_MAJOR_VERSION 0x04 +#define TAP_DRIVER_MINOR_VERSION 0x02 + +#endif + +#define TAP_DRIVER_VENDOR_VERSION ((TAP_DRIVER_MAJOR_VERSION << 16) | TAP_DRIVER_MINOR_VERSION) + +// +// Define the NDIS miniport interface version that this driver targets. +// +#if defined(NDIS60_MINIPORT) +# define TAP_NDIS_MAJOR_VERSION 6 +# define TAP_NDIS_MINOR_VERSION 0 +#elif defined(NDIS61_MINIPORT) +# define TAP_NDIS_MAJOR_VERSION 6 +# define TAP_NDIS_MINOR_VERSION 1 +#elif defined(NDIS620_MINIPORT) +# define TAP_NDIS_MAJOR_VERSION 6 +# define TAP_NDIS_MINOR_VERSION 20 +#elif defined(NDIS630_MINIPORT) +# define TAP_NDIS_MAJOR_VERSION 6 +# define TAP_NDIS_MINOR_VERSION 30 +#else +#define TAP_NDIS_MAJOR_VERSION 5 +#define TAP_NDIS_MINOR_VERSION 0 +#endif + +//=========================================================== +// Driver constants +//=========================================================== + +#define ETHERNET_HEADER_SIZE (sizeof (ETH_HEADER)) +//#define ETHERNET_MTU 1500 +#define ETHERNET_MTU 2800 +#define ETHERNET_PACKET_SIZE (ETHERNET_MTU + ETHERNET_HEADER_SIZE) +#define DEFAULT_PACKET_LOOKAHEAD (ETHERNET_PACKET_SIZE) +#define VLAN_TAG_SIZE 4 + +//=========================================================== +// Medium properties +//=========================================================== + +#define TAP_FRAME_HEADER_SIZE ETHERNET_HEADER_SIZE +#define TAP_FRAME_MAX_DATA_SIZE ETHERNET_MTU +#define TAP_MAX_FRAME_SIZE (TAP_FRAME_HEADER_SIZE + TAP_FRAME_MAX_DATA_SIZE) +#define TAP_MIN_FRAME_SIZE 60 + +#define TAP_MEDIUM_TYPE NdisMedium802_3 + +//=========================================================== +// Physical adapter properties +//=========================================================== + +// The bus that connects the adapter to the PC. +// (Example: PCI adapters should use NdisInterfacePci). +#define TAP_INTERFACE_TYPE NdisInterfaceInternal + +#define TAP_VENDOR_DESC PRODUCT_TAP_WIN_DEVICE_DESCRIPTION + +// Highest byte is the NIC byte plus three vendor bytes. This is normally +// obtained from the NIC. +#define TAP_VENDOR_ID 0x00FFFFFF + +// If you have physical hardware on 802.3, use NdisPhysicalMedium802_3. +#define TAP_PHYSICAL_MEDIUM NdisPhysicalMediumUnspecified + +// Claim to be 100mbps duplex +#define MEGABITS_PER_SECOND 1000000ULL +#define TAP_XMIT_SPEED (100ULL*MEGABITS_PER_SECOND) +#define TAP_RECV_SPEED (100ULL*MEGABITS_PER_SECOND) + +// Max number of multicast addresses supported in hardware +#define TAP_MAX_MCAST_LIST 128 + +#define TAP_MAX_LOOKAHEAD TAP_FRAME_MAX_DATA_SIZE +#define TAP_BUFFER_SIZE TAP_MAX_FRAME_SIZE + +// Set this value to TRUE if there is a physical adapter. +#define TAP_HAS_PHYSICAL_CONNECTOR FALSE +#define TAP_ACCESS_TYPE NET_IF_ACCESS_BROADCAST +#define TAP_DIRECTION_TYPE NET_IF_DIRECTION_SENDRECEIVE +#define TAP_CONNECTION_TYPE NET_IF_CONNECTION_DEDICATED + +// This value must match the *IfType in the driver .inf file +#define TAP_IFTYPE IF_TYPE_ETHERNET_CSMACD + +// +// This is a virtual device, so it can tolerate surprise removal and +// suspend. Ensure the correct flags are set for your hardware. +// +#define TAP_ADAPTER_ATTRIBUTES_FLAGS (\ + NDIS_MINIPORT_ATTRIBUTES_SURPRISE_REMOVE_OK | NDIS_MINIPORT_ATTRIBUTES_NDIS_WDM) + +#define TAP_SUPPORTED_FILTERS ( \ + NDIS_PACKET_TYPE_DIRECTED | \ + NDIS_PACKET_TYPE_MULTICAST | \ + NDIS_PACKET_TYPE_BROADCAST | \ + NDIS_PACKET_TYPE_ALL_LOCAL | \ + NDIS_PACKET_TYPE_PROMISCUOUS | \ + NDIS_PACKET_TYPE_ALL_MULTICAST) + +//#define TAP_MAX_MCAST_LIST 128 // Max length of multicast address list + +// +// Specify a bitmask that defines optional properties of the NIC. +// This miniport indicates receive with NdisMIndicateReceiveNetBufferLists +// function. Such a driver should set this NDIS_MAC_OPTION_TRANSFERS_NOT_PEND +// flag. +// +// NDIS_MAC_OPTION_NO_LOOPBACK tells NDIS that NIC has no internal +// loopback support so NDIS will manage loopbacks on behalf of +// this driver. +// +// NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA tells the protocol that +// our receive buffer is not on a device-specific card. If +// NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA is not set, multi-buffer +// indications are copied to a single flat buffer. +// + +#define TAP_MAC_OPTIONS (\ + NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA | \ + NDIS_MAC_OPTION_TRANSFERS_NOT_PEND | \ + NDIS_MAC_OPTION_NO_LOOPBACK) + +#define TAP_ADAPTER_CHECK_FOR_HANG_TIME_IN_SECONDS 4 + + +// NDIS 6.x miniports must support all counters in OID_GEN_STATISTICS. +#define TAP_SUPPORTED_STATISTICS (\ + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_RCV | \ + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_RCV | \ + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_RCV | \ + NDIS_STATISTICS_FLAGS_VALID_BYTES_RCV | \ + NDIS_STATISTICS_FLAGS_VALID_RCV_DISCARDS | \ + NDIS_STATISTICS_FLAGS_VALID_RCV_ERROR | \ + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_XMIT | \ + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_XMIT | \ + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_XMIT | \ + NDIS_STATISTICS_FLAGS_VALID_BYTES_XMIT | \ + NDIS_STATISTICS_FLAGS_VALID_XMIT_ERROR | \ + NDIS_STATISTICS_FLAGS_VALID_XMIT_DISCARDS | \ + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_RCV | \ + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_RCV | \ + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_RCV | \ + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_XMIT | \ + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_XMIT | \ + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_XMIT) + + +#define MINIMUM_MTU 576 // USE TCP Minimum MTU +#define MAXIMUM_MTU 65536 // IP maximum MTU + +#define PACKET_QUEUE_SIZE 64 // tap -> userspace queue size +#define IRP_QUEUE_SIZE 16 // max number of simultaneous i/o operations from userspace +#define INJECT_QUEUE_SIZE 16 // DHCP/ARP -> tap injection queue + +#define TAP_LITTLE_ENDIAN // affects ntohs, htonl, etc. functions diff --git a/windows/TapDriver6/device.c b/windows/TapDriver6/device.c new file mode 100644 index 0000000..7367143 --- /dev/null +++ b/windows/TapDriver6/device.c @@ -0,0 +1,1209 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// +// Include files. +// + +#include "tap.h" +#include // for SDDLs + +//====================================================================== +// TAP Win32 Device I/O Callbacks +//====================================================================== + +#ifdef ALLOC_PRAGMA +#pragma alloc_text( PAGE, TapDeviceCreate) +#pragma alloc_text( PAGE, TapDeviceControl) +#pragma alloc_text( PAGE, TapDeviceCleanup) +#pragma alloc_text( PAGE, TapDeviceClose) +#endif // ALLOC_PRAGMA + +//=================================================================== +// Go back to default TAP mode from Point-To-Point mode. +// Also reset (i.e. disable) DHCP Masq mode. +//=================================================================== +VOID tapResetAdapterState( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + /* + // Point-To-Point + Adapter->m_tun = FALSE; + Adapter->m_localIP = 0; + Adapter->m_remoteNetwork = 0; + Adapter->m_remoteNetmask = 0; + NdisZeroMemory (&Adapter->m_TapToUser, sizeof (Adapter->m_TapToUser)); + NdisZeroMemory (&Adapter->m_UserToTap, sizeof (Adapter->m_UserToTap)); + NdisZeroMemory (&Adapter->m_UserToTap_IPv6, sizeof (Adapter->m_UserToTap_IPv6)); + */ + + // DHCP Masq + /* + Adapter->m_dhcp_enabled = FALSE; + Adapter->m_dhcp_server_arp = FALSE; + Adapter->m_dhcp_user_supplied_options_buffer_len = 0; + Adapter->m_dhcp_addr = 0; + Adapter->m_dhcp_netmask = 0; + Adapter->m_dhcp_server_ip = 0; + Adapter->m_dhcp_lease_time = 0; + Adapter->m_dhcp_received_discover = FALSE; + Adapter->m_dhcp_bad_requests = 0; + NdisZeroMemory (Adapter->m_dhcp_server_mac, MACADDR_SIZE); + */ +} + +// IRP_MJ_CREATE +NTSTATUS +TapDeviceCreate( + PDEVICE_OBJECT DeviceObject, + PIRP Irp + ) +/*++ + +Routine Description: + + This routine is called by the I/O system when the device is opened. + + No action is performed other than completing the request successfully. + +Arguments: + + DeviceObject - a pointer to the object that represents the device + that I/O is to be done on. + + Irp - a pointer to the I/O Request Packet for this request. + +Return Value: + + NT status code + +--*/ +{ + NDIS_STATUS status; + PIO_STACK_LOCATION irpSp;// Pointer to current stack location + PTAP_ADAPTER_CONTEXT adapter = NULL; + PFILE_OBJECT originalFileObject; + + PAGED_CODE(); + + DEBUGP (("[TAP] --> TapDeviceCreate\n")); + + irpSp = IoGetCurrentIrpStackLocation(Irp); + + // + // Invalidate file context + // + irpSp->FileObject->FsContext = NULL; + irpSp->FileObject->FsContext2 = NULL; + + // + // Find adapter context for this device. + // ------------------------------------- + // Returns with added reference on adapter context. + // + adapter = tapAdapterContextFromDeviceObject(DeviceObject); + + // Insure that adapter exists. + ASSERT(adapter); + + if(adapter == NULL ) + { + DEBUGP (("[TAP] release [%d.%d] open request; adapter not found\n", + TAP_DRIVER_MAJOR_VERSION, + TAP_DRIVER_MINOR_VERSION + )); + + Irp->IoStatus.Status = STATUS_DEVICE_DOES_NOT_EXIST; + Irp->IoStatus.Information = 0; + + IoCompleteRequest( Irp, IO_NO_INCREMENT ); + + return STATUS_DEVICE_DOES_NOT_EXIST; + } + + DEBUGP(("[%s] [TAP] release [%d.%d] open request (TapFileIsOpen=%d)\n", + MINIPORT_INSTANCE_ID(adapter), + TAP_DRIVER_MAJOR_VERSION, + TAP_DRIVER_MINOR_VERSION, + adapter->TapFileIsOpen + )); + + // Enforce exclusive access + originalFileObject = InterlockedCompareExchangePointer( + &adapter->TapFileObject, + irpSp->FileObject, + NULL + ); + + if(originalFileObject == NULL) + { + irpSp->FileObject->FsContext = adapter; // Quick reference + + status = STATUS_SUCCESS; + } + else + { + status = STATUS_UNSUCCESSFUL; + } + + // Release the lock. + //tapAdapterReleaseLock(adapter,FALSE); + + if(status == STATUS_SUCCESS) + { + // Reset adapter state on successful open. + tapResetAdapterState(adapter); + + adapter->TapFileIsOpen = 1; // Legacy... + + // NOTE!!! Reference added by tapAdapterContextFromDeviceObject + // will be removed when file is closed. + } + else + { + DEBUGP (("[%s] TAP is presently unavailable (TapFileIsOpen=%d)\n", + MINIPORT_INSTANCE_ID(adapter), adapter->TapFileIsOpen + )); + + NOTE_ERROR(); + + // Remove reference added by tapAdapterContextFromDeviceObject. + tapAdapterContextDereference(adapter); + } + + // Complete the IRP. + Irp->IoStatus.Status = status; + Irp->IoStatus.Information = 0; + + IoCompleteRequest( Irp, IO_NO_INCREMENT ); + + DEBUGP (("[TAP] <-- TapDeviceCreate; status = %8.8X\n",status)); + + return status; +} + +//=================================================== +// Tell Windows whether the TAP device should be +// considered "connected" or "disconnected". +// +// Allows application control of media connect state. +//=================================================== +VOID +tapSetMediaConnectStatus( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in BOOLEAN LogicalMediaState + ) +{ + NDIS_STATUS_INDICATION statusIndication; + NDIS_LINK_STATE linkState; + + NdisZeroMemory(&statusIndication, sizeof(NDIS_STATUS_INDICATION)); + NdisZeroMemory(&linkState, sizeof(NDIS_LINK_STATE)); + + // + // Fill in object headers + // + statusIndication.Header.Type = NDIS_OBJECT_TYPE_STATUS_INDICATION; + statusIndication.Header.Revision = NDIS_STATUS_INDICATION_REVISION_1; + statusIndication.Header.Size = sizeof(NDIS_STATUS_INDICATION); + + linkState.Header.Revision = NDIS_LINK_STATE_REVISION_1; + linkState.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + linkState.Header.Size = sizeof(NDIS_LINK_STATE); + + // + // Link state buffer + // + if(Adapter->LogicalMediaState == TRUE) + { + linkState.MediaConnectState = MediaConnectStateConnected; + } + + linkState.MediaDuplexState = MediaDuplexStateFull; + linkState.RcvLinkSpeed = TAP_RECV_SPEED; + linkState.XmitLinkSpeed = TAP_XMIT_SPEED; + + // + // Fill in the status buffer + // + statusIndication.StatusCode = NDIS_STATUS_LINK_STATE; + statusIndication.SourceHandle = Adapter->MiniportAdapterHandle; + statusIndication.DestinationHandle = NULL; + statusIndication.RequestId = 0; + + statusIndication.StatusBuffer = &linkState; + statusIndication.StatusBufferSize = sizeof(NDIS_LINK_STATE); + + // Fill in new media connect state. + if ( (Adapter->LogicalMediaState != LogicalMediaState) && !Adapter->MediaStateAlwaysConnected) + { + Adapter->LogicalMediaState = LogicalMediaState; + + if (LogicalMediaState == TRUE) + { + linkState.MediaConnectState = MediaConnectStateConnected; + + DEBUGP (("[TAP] Set MediaConnectState: Connected.\n")); + } + else + { + linkState.MediaConnectState = MediaConnectStateDisconnected; + + DEBUGP (("[TAP] Set MediaConnectState: Disconnected.\n")); + } + } + + // Make the status indication. + if(Adapter->Locked.AdapterState != MiniportHaltedState) + { + NdisMIndicateStatusEx(Adapter->MiniportAdapterHandle, &statusIndication); + } +} + +/* +//====================================================== +// If DHCP mode is used together with tun +// mode, consider the fact that the P2P remote subnet +// might enclose the DHCP masq server address. +//====================================================== +VOID +CheckIfDhcpAndTunMode ( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + if (Adapter->m_tun && Adapter->m_dhcp_enabled) + { + if ((Adapter->m_dhcp_server_ip & Adapter->m_remoteNetmask) == Adapter->m_remoteNetwork) + { + ETH_COPY_NETWORK_ADDRESS (Adapter->m_dhcp_server_mac, Adapter->m_TapToUser.dest); + Adapter->m_dhcp_server_arp = FALSE; + } + } +} +*/ + +// IRP_MJ_DEVICE_CONTROL callback. +NTSTATUS +TapDeviceControl( + PDEVICE_OBJECT DeviceObject, + PIRP Irp + ) + +/*++ + +Routine Description: + + This routine is called by the I/O system to perform a device I/O + control function. + +Arguments: + + DeviceObject - a pointer to the object that represents the device + that I/O is to be done on. + + Irp - a pointer to the I/O Request Packet for this request. + +Return Value: + + NT status code + +--*/ + +{ + NTSTATUS ntStatus = STATUS_SUCCESS; // Assume success + PIO_STACK_LOCATION irpSp; // Pointer to current stack location + PTAP_ADAPTER_CONTEXT adapter = NULL; + ULONG inBufLength; // Input buffer length + ULONG outBufLength; // Output buffer length + PCHAR inBuf, outBuf; // pointer to Input and output buffer + PMDL mdl = NULL; + PCHAR buffer = NULL; + + PAGED_CODE(); + + irpSp = IoGetCurrentIrpStackLocation( Irp ); + + // + // Fetch adapter context for this device. + // -------------------------------------- + // Adapter pointer was stashed in FsContext when handle was opened. + // + adapter = (PTAP_ADAPTER_CONTEXT )(irpSp->FileObject)->FsContext; + + ASSERT(adapter); + + inBufLength = irpSp->Parameters.DeviceIoControl.InputBufferLength; + outBufLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength; + + if (!inBufLength || !outBufLength) + { + ntStatus = STATUS_INVALID_PARAMETER; + goto End; + } + + // + // Determine which I/O control code was specified. + // + switch ( irpSp->Parameters.DeviceIoControl.IoControlCode ) + { + case TAP_WIN_IOCTL_GET_MAC: + { + if (outBufLength >= MACADDR_SIZE ) + { + ETH_COPY_NETWORK_ADDRESS( + Irp->AssociatedIrp.SystemBuffer, + adapter->CurrentAddress + ); + + Irp->IoStatus.Information = MACADDR_SIZE; + } + else + { + NOTE_ERROR(); + Irp->IoStatus.Status = ntStatus = STATUS_BUFFER_TOO_SMALL; + } + } + break; + + case TAP_WIN_IOCTL_GET_VERSION: + { + const ULONG size = sizeof (ULONG) * 3; + + if (outBufLength >= size) + { + ((PULONG) (Irp->AssociatedIrp.SystemBuffer))[0] + = TAP_DRIVER_MAJOR_VERSION; + + ((PULONG) (Irp->AssociatedIrp.SystemBuffer))[1] + = TAP_DRIVER_MINOR_VERSION; + + ((PULONG) (Irp->AssociatedIrp.SystemBuffer))[2] +#if DBG + = 1; +#else + = 0; +#endif + Irp->IoStatus.Information = size; + } + else + { + NOTE_ERROR(); + Irp->IoStatus.Status = ntStatus = STATUS_BUFFER_TOO_SMALL; + } + } + break; + + case TAP_WIN_IOCTL_GET_MTU: + { + const ULONG size = sizeof (ULONG) * 1; + + if (outBufLength >= size) + { + ((PULONG) (Irp->AssociatedIrp.SystemBuffer))[0] + = adapter->MtuSize; + + Irp->IoStatus.Information = size; + } + else + { + NOTE_ERROR(); + Irp->IoStatus.Status = ntStatus = STATUS_BUFFER_TOO_SMALL; + } + } + break; + + // Allow ZeroTier One to get multicast memberships at the L2 level in a + // protocol-neutral manner. + case TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS: + { + if (outBufLength < TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS_OUTPUT_BUF_SIZE) { + /* output buffer too small */ + NOTE_ERROR (); + Irp->IoStatus.Status = ntStatus = STATUS_BUFFER_TOO_SMALL; + } else { + char *out = (char *)Irp->AssociatedIrp.SystemBuffer; + char *end = out + TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS_OUTPUT_BUF_SIZE; + unsigned long i,j; + for(i=0;iulMCListSize;++i) { + if (i >= TAP_MAX_MCAST_LIST) + break; + for(j=0;j<6;++j) + *(out++) = adapter->MCList[i][j]; + if (out >= end) + break; + } + while (out < end) + *(out++) = (char)0; + Irp->IoStatus.Information = TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS_OUTPUT_BUF_SIZE; + Irp->IoStatus.Status = ntStatus = STATUS_SUCCESS; + } + break; + } + + +#if 0 + case TAP_WIN_IOCTL_CONFIG_TUN: + { + if(inBufLength >= sizeof(IPADDR)*3) + { + MACADDR dest; + + adapter->m_tun = FALSE; + + GenerateRelatedMAC (dest, adapter->CurrentAddress, 1); + + adapter->m_localIP = ((IPADDR*) (Irp->AssociatedIrp.SystemBuffer))[0]; + adapter->m_remoteNetwork = ((IPADDR*) (Irp->AssociatedIrp.SystemBuffer))[1]; + adapter->m_remoteNetmask = ((IPADDR*) (Irp->AssociatedIrp.SystemBuffer))[2]; + + // Sanity check on network/netmask + if ((adapter->m_remoteNetwork & adapter->m_remoteNetmask) != adapter->m_remoteNetwork) + { + NOTE_ERROR(); + Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER; + break; + } + + ETH_COPY_NETWORK_ADDRESS (adapter->m_TapToUser.src, adapter->CurrentAddress); + ETH_COPY_NETWORK_ADDRESS (adapter->m_TapToUser.dest, dest); + ETH_COPY_NETWORK_ADDRESS (adapter->m_UserToTap.src, dest); + ETH_COPY_NETWORK_ADDRESS (adapter->m_UserToTap.dest, adapter->CurrentAddress); + + adapter->m_TapToUser.proto = adapter->m_UserToTap.proto = htons (NDIS_ETH_TYPE_IPV4); + adapter->m_UserToTap_IPv6 = adapter->m_UserToTap; + adapter->m_UserToTap_IPv6.proto = htons(NDIS_ETH_TYPE_IPV6); + + adapter->m_tun = TRUE; + + CheckIfDhcpAndTunMode (adapter); + + Irp->IoStatus.Information = 1; // Simple boolean value + + DEBUGP (("[TAP] Set TUN mode.\n")); + } + else + { + NOTE_ERROR(); + Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER; + } + } + break; + + case TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT: + { + if(inBufLength >= sizeof(IPADDR)*2) + { + MACADDR dest; + + adapter->m_tun = FALSE; + + GenerateRelatedMAC (dest, adapter->CurrentAddress, 1); + + adapter->m_localIP = ((IPADDR*) (Irp->AssociatedIrp.SystemBuffer))[0]; + adapter->m_remoteNetwork = ((IPADDR*) (Irp->AssociatedIrp.SystemBuffer))[1]; + adapter->m_remoteNetmask = ~0; + + ETH_COPY_NETWORK_ADDRESS (adapter->m_TapToUser.src, adapter->CurrentAddress); + ETH_COPY_NETWORK_ADDRESS (adapter->m_TapToUser.dest, dest); + ETH_COPY_NETWORK_ADDRESS (adapter->m_UserToTap.src, dest); + ETH_COPY_NETWORK_ADDRESS (adapter->m_UserToTap.dest, adapter->CurrentAddress); + + adapter->m_TapToUser.proto = adapter->m_UserToTap.proto = htons (NDIS_ETH_TYPE_IPV4); + adapter->m_UserToTap_IPv6 = adapter->m_UserToTap; + adapter->m_UserToTap_IPv6.proto = htons(NDIS_ETH_TYPE_IPV6); + + adapter->m_tun = TRUE; + + CheckIfDhcpAndTunMode (adapter); + + Irp->IoStatus.Information = 1; // Simple boolean value + + DEBUGP (("[TAP] Set P2P mode.\n")); + } + else + { + NOTE_ERROR(); + Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER; + } + } + break; +#endif + +#if 0 + case TAP_WIN_IOCTL_CONFIG_DHCP_MASQ: + { + if(inBufLength >= sizeof(IPADDR)*4) + { + adapter->m_dhcp_enabled = FALSE; + adapter->m_dhcp_server_arp = FALSE; + adapter->m_dhcp_user_supplied_options_buffer_len = 0; + + // Adapter IP addr / netmask + adapter->m_dhcp_addr = + ((IPADDR*) (Irp->AssociatedIrp.SystemBuffer))[0]; + adapter->m_dhcp_netmask = + ((IPADDR*) (Irp->AssociatedIrp.SystemBuffer))[1]; + + // IP addr of DHCP masq server + adapter->m_dhcp_server_ip = + ((IPADDR*) (Irp->AssociatedIrp.SystemBuffer))[2]; + + // Lease time in seconds + adapter->m_dhcp_lease_time = + ((IPADDR*) (Irp->AssociatedIrp.SystemBuffer))[3]; + + GenerateRelatedMAC( + adapter->m_dhcp_server_mac, + adapter->CurrentAddress, + 2 + ); + + adapter->m_dhcp_enabled = TRUE; + adapter->m_dhcp_server_arp = TRUE; + + CheckIfDhcpAndTunMode (adapter); + + Irp->IoStatus.Information = 1; // Simple boolean value + + DEBUGP (("[TAP] Configured DHCP MASQ.\n")); + } + else + { + NOTE_ERROR(); + Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER; + } + } + break; + + case TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT: + { + if (inBufLength <= DHCP_USER_SUPPLIED_OPTIONS_BUFFER_SIZE + && adapter->m_dhcp_enabled) + { + adapter->m_dhcp_user_supplied_options_buffer_len = 0; + + NdisMoveMemory( + adapter->m_dhcp_user_supplied_options_buffer, + Irp->AssociatedIrp.SystemBuffer, + inBufLength + ); + + adapter->m_dhcp_user_supplied_options_buffer_len = + inBufLength; + + Irp->IoStatus.Information = 1; // Simple boolean value + + DEBUGP (("[TAP] Set DHCP OPT.\n")); + } + else + { + NOTE_ERROR(); + Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER; + } + } + break; +#endif + +#if 0 + case TAP_WIN_IOCTL_GET_INFO: + { + char state[16]; + + // Fetch adapter (miniport) state. + if (tapAdapterSendAndReceiveReady(adapter) == NDIS_STATUS_SUCCESS) + state[0] = 'A'; + else + state[0] = 'a'; + + if (tapAdapterReadAndWriteReady(adapter)) + state[1] = 'T'; + else + state[1] = 't'; + + state[2] = '0' + adapter->CurrentPowerState; + + if (adapter->MediaStateAlwaysConnected) + state[3] = 'C'; + else + state[3] = 'c'; + + state[4] = '\0'; + + // BUGBUG!!! What follows, and is not yet implemented, is a real mess. + // BUGBUG!!! Tied closely to the NDIS 5 implementation. Need to map + // as much as possible to the NDIS 6 implementation. + Irp->IoStatus.Status = ntStatus = RtlStringCchPrintfExA ( + ((LPTSTR) (Irp->AssociatedIrp.SystemBuffer)), + outBufLength, + NULL, + NULL, + STRSAFE_FILL_BEHIND_NULL | STRSAFE_IGNORE_NULLS, +#if PACKET_TRUNCATION_CHECK + "State=%s Err=[%s/%d] #O=%d Tx=[%d,%d,%d] Rx=[%d,%d,%d] IrpQ=[%d,%d,%d] PktQ=[%d,%d,%d] InjQ=[%d,%d,%d]", +#else + "State=%s Err=[%s/%d] #O=%d Tx=[%d,%d] Rx=[%d,%d] IrpQ=[%d,%d,%d] PktQ=[%d,%d,%d] InjQ=[%d,%d,%d]", +#endif + state, + g_LastErrorFilename, + g_LastErrorLineNumber, + (int)adapter->TapFileOpenCount, + (int)(adapter->FramesTxDirected + adapter->FramesTxMulticast + adapter->FramesTxBroadcast), + (int)adapter->TransmitFailuresOther, +#if PACKET_TRUNCATION_CHECK + (int)adapter->m_TxTrunc, +#endif + (int)adapter->m_Rx, + (int)adapter->m_RxErr, +#if PACKET_TRUNCATION_CHECK + (int)adapter->m_RxTrunc, +#endif + (int)adapter->PendingReadIrpQueue.Count, + (int)adapter->PendingReadIrpQueue.MaxCount, + (int)IRP_QUEUE_SIZE, // Ignored in NDIS 6 driver... + + (int)adapter->SendPacketQueue.Count, + (int)adapter->SendPacketQueue.MaxCount, + (int)PACKET_QUEUE_SIZE, + + (int)0, // adapter->InjectPacketQueue.Count - Unused + (int)0, // adapter->InjectPacketQueue.MaxCount - Unused + (int)INJECT_QUEUE_SIZE + ); + + Irp->IoStatus.Information = outBufLength; + + // BUGBUG!!! Fail because this is not completely implemented. + ntStatus = STATUS_INVALID_DEVICE_REQUEST; + } +#endif + +#if DBG + case TAP_WIN_IOCTL_GET_LOG_LINE: + { + if (GetDebugLine( (LPTSTR)Irp->AssociatedIrp.SystemBuffer,outBufLength)) + { + Irp->IoStatus.Status = ntStatus = STATUS_SUCCESS; + } + else + { + Irp->IoStatus.Status = ntStatus = STATUS_UNSUCCESSFUL; + } + + Irp->IoStatus.Information = outBufLength; + + break; + } +#endif + + case TAP_WIN_IOCTL_SET_MEDIA_STATUS: + { + if(inBufLength >= sizeof(ULONG)) + { + ULONG parm = ((PULONG) (Irp->AssociatedIrp.SystemBuffer))[0]; + tapSetMediaConnectStatus (adapter, (BOOLEAN) parm); + Irp->IoStatus.Information = 1; + } + else + { + NOTE_ERROR(); + Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER; + } + } + break; + + default: + + // + // The specified I/O control code is unrecognized by this driver. + // + ntStatus = STATUS_INVALID_DEVICE_REQUEST; + break; + } + +End: + + // + // Finish the I/O operation by simply completing the packet and returning + // the same status as in the packet itself. + // + Irp->IoStatus.Status = ntStatus; + + IoCompleteRequest( Irp, IO_NO_INCREMENT ); + + return ntStatus; +} + +// Flush the pending read IRP queue. +VOID +tapFlushIrpQueues( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + + DEBUGP (("[TAP] tapFlushIrpQueues: Flushing %d pending read IRPs\n", + Adapter->PendingReadIrpQueue.Count)); + + tapIrpCsqFlush(&Adapter->PendingReadIrpQueue); +} + +// IRP_MJ_CLEANUP +NTSTATUS +TapDeviceCleanup( + PDEVICE_OBJECT DeviceObject, + PIRP Irp + ) +/*++ + +Routine Description: + + Receipt of this request indicates that the last handle for a file + object that is associated with the target device object has been closed + (but, due to outstanding I/O requests, might not have been released). + + A driver that holds pending IRPs internally must implement a routine for + IRP_MJ_CLEANUP. When the routine is called, the driver should cancel all + the pending IRPs that belong to the file object identified by the IRP_MJ_CLEANUP + call. + + In other words, it should cancel all the IRPs that have the same file-object + pointer as the one supplied in the current I/O stack location of the IRP for the + IRP_MJ_CLEANUP call. Of course, IRPs belonging to other file objects should + not be canceled. Also, if an outstanding IRP is completed immediately, the + driver does not have to cancel it. + +Arguments: + + DeviceObject - a pointer to the object that represents the device + to be cleaned up. + + Irp - a pointer to the I/O Request Packet for this request. + +Return Value: + + NT status code + +--*/ + +{ + NDIS_STATUS status = NDIS_STATUS_SUCCESS; // Always succeed. + PIO_STACK_LOCATION irpSp; // Pointer to current stack location + PTAP_ADAPTER_CONTEXT adapter = NULL; + + PAGED_CODE(); + + DEBUGP (("[TAP] --> TapDeviceCleanup\n")); + + irpSp = IoGetCurrentIrpStackLocation(Irp); + + // + // Fetch adapter context for this device. + // -------------------------------------- + // Adapter pointer was stashed in FsContext when handle was opened. + // + adapter = (PTAP_ADAPTER_CONTEXT )(irpSp->FileObject)->FsContext; + + // Insure that adapter exists. + ASSERT(adapter); + + if(adapter == NULL ) + { + DEBUGP (("[TAP] release [%d.%d] cleanup request; adapter not found\n", + TAP_DRIVER_MAJOR_VERSION, + TAP_DRIVER_MINOR_VERSION + )); + } + + if(adapter != NULL ) + { + adapter->TapFileIsOpen = 0; // Legacy... + + // Disconnect from media. + tapSetMediaConnectStatus(adapter,FALSE); + + // Reset adapter state when cleaning up; + tapResetAdapterState(adapter); + + // BUGBUG!!! Use RemoveLock??? + + // + // Flush pending send TAP packet queue. + // + tapFlushSendPacketQueue(adapter); + + ASSERT(adapter->SendPacketQueue.Count == 0); + + // + // Flush the pending IRP queues + // + tapFlushIrpQueues(adapter); + + ASSERT(adapter->PendingReadIrpQueue.Count == 0); + } + + // Complete the IRP. + Irp->IoStatus.Status = status; + Irp->IoStatus.Information = 0; + + IoCompleteRequest( Irp, IO_NO_INCREMENT ); + + DEBUGP (("[TAP] <-- TapDeviceCleanup; status = %8.8X\n",status)); + + return status; +} + +// IRP_MJ_CLOSE +NTSTATUS +TapDeviceClose( + PDEVICE_OBJECT DeviceObject, + PIRP Irp + ) +/*++ + +Routine Description: + + Receipt of this request indicates that the last handle of the file + object that is associated with the target device object has been closed + and released. + + All outstanding I/O requests have been completed or canceled. + +Arguments: + + DeviceObject - a pointer to the object that represents the device + to be closed. + + Irp - a pointer to the I/O Request Packet for this request. + +Return Value: + + NT status code + +--*/ + +{ + NDIS_STATUS status = NDIS_STATUS_SUCCESS; // Always succeed. + PIO_STACK_LOCATION irpSp; // Pointer to current stack location + PTAP_ADAPTER_CONTEXT adapter = NULL; + + PAGED_CODE(); + + DEBUGP (("[TAP] --> TapDeviceClose\n")); + + irpSp = IoGetCurrentIrpStackLocation(Irp); + + // + // Fetch adapter context for this device. + // -------------------------------------- + // Adapter pointer was stashed in FsContext when handle was opened. + // + adapter = (PTAP_ADAPTER_CONTEXT )(irpSp->FileObject)->FsContext; + + // Insure that adapter exists. + ASSERT(adapter); + + if(adapter == NULL ) + { + DEBUGP (("[TAP] release [%d.%d] close request; adapter not found\n", + TAP_DRIVER_MAJOR_VERSION, + TAP_DRIVER_MINOR_VERSION + )); + } + + if(adapter != NULL ) + { + if(adapter->TapFileObject == NULL) + { + // Should never happen!!! + ASSERT(FALSE); + } + else + { + ASSERT(irpSp->FileObject->FsContext == adapter); + + ASSERT(adapter->TapFileObject == irpSp->FileObject); + } + + adapter->TapFileObject = NULL; + irpSp->FileObject = NULL; + + // Remove reference added by when handle was opened. + tapAdapterContextDereference(adapter); + } + + // Complete the IRP. + Irp->IoStatus.Status = status; + Irp->IoStatus.Information = 0; + + IoCompleteRequest( Irp, IO_NO_INCREMENT ); + + DEBUGP (("[TAP] <-- TapDeviceClose; status = %8.8X\n",status)); + + return status; +} + +NTSTATUS +tapConcatenateNdisStrings( + __inout PNDIS_STRING DestinationString, + __in_opt PNDIS_STRING SourceString1, + __in_opt PNDIS_STRING SourceString2, + __in_opt PNDIS_STRING SourceString3 + ) +{ + NTSTATUS status; + + ASSERT(SourceString1 && SourceString2 && SourceString3); + + status = RtlAppendUnicodeStringToString( + DestinationString, + SourceString1 + ); + + if(status == STATUS_SUCCESS) + { + status = RtlAppendUnicodeStringToString( + DestinationString, + SourceString2 + ); + + if(status == STATUS_SUCCESS) + { + status = RtlAppendUnicodeStringToString( + DestinationString, + SourceString3 + ); + } + } + + return status; +} + +NTSTATUS +tapMakeDeviceNames( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + NDIS_STATUS status; + NDIS_STRING deviceNamePrefix = NDIS_STRING_CONST("\\Device\\"); + NDIS_STRING tapNameSuffix = NDIS_STRING_CONST(".tap"); + + // Generate DeviceName from NetCfgInstanceId. + Adapter->DeviceName.Buffer = Adapter->DeviceNameBuffer; + Adapter->DeviceName.MaximumLength = sizeof(Adapter->DeviceNameBuffer); + + status = tapConcatenateNdisStrings( + &Adapter->DeviceName, + &deviceNamePrefix, + &Adapter->NetCfgInstanceId, + &tapNameSuffix + ); + + if(status == STATUS_SUCCESS) + { + NDIS_STRING linkNamePrefix = NDIS_STRING_CONST("\\DosDevices\\Global\\"); + + Adapter->LinkName.Buffer = Adapter->LinkNameBuffer; + Adapter->LinkName.MaximumLength = sizeof(Adapter->LinkNameBuffer); + + status = tapConcatenateNdisStrings( + &Adapter->LinkName, + &linkNamePrefix, + &Adapter->NetCfgInstanceId, + &tapNameSuffix + ); + } + + return status; +} + +NDIS_STATUS +CreateTapDevice( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + NDIS_STATUS status; + NDIS_DEVICE_OBJECT_ATTRIBUTES deviceAttribute; + PDRIVER_DISPATCH dispatchTable[IRP_MJ_MAXIMUM_FUNCTION+1]; + + DEBUGP (("[TAP] version [%d.%d] creating tap device: %wZ\n", + TAP_DRIVER_MAJOR_VERSION, + TAP_DRIVER_MINOR_VERSION, + &Adapter->NetCfgInstanceId)); + + // Generate DeviceName and LinkName from NetCfgInstanceId. + status = tapMakeDeviceNames(Adapter); + + if (NT_SUCCESS(status)) + { + DEBUGP (("[TAP] DeviceName: %wZ\n",&Adapter->DeviceName)); + DEBUGP (("[TAP] LinkName: %wZ\n",&Adapter->LinkName)); + + // Initialize dispatch table. + NdisZeroMemory(dispatchTable, (IRP_MJ_MAXIMUM_FUNCTION+1) * sizeof(PDRIVER_DISPATCH)); + + dispatchTable[IRP_MJ_CREATE] = TapDeviceCreate; + dispatchTable[IRP_MJ_CLEANUP] = TapDeviceCleanup; + dispatchTable[IRP_MJ_CLOSE] = TapDeviceClose; + dispatchTable[IRP_MJ_READ] = TapDeviceRead; + dispatchTable[IRP_MJ_WRITE] = TapDeviceWrite; + dispatchTable[IRP_MJ_DEVICE_CONTROL] = TapDeviceControl; + + // + // Create a device object and register dispatch handlers + // + NdisZeroMemory(&deviceAttribute, sizeof(NDIS_DEVICE_OBJECT_ATTRIBUTES)); + + deviceAttribute.Header.Type = NDIS_OBJECT_TYPE_DEVICE_OBJECT_ATTRIBUTES; + deviceAttribute.Header.Revision = NDIS_DEVICE_OBJECT_ATTRIBUTES_REVISION_1; + deviceAttribute.Header.Size = sizeof(NDIS_DEVICE_OBJECT_ATTRIBUTES); + + deviceAttribute.DeviceName = &Adapter->DeviceName; + deviceAttribute.SymbolicName = &Adapter->LinkName; + deviceAttribute.MajorFunctions = &dispatchTable[0]; + //deviceAttribute.ExtensionSize = sizeof(FILTER_DEVICE_EXTENSION); + +#if ENABLE_NONADMIN + if(Adapter->AllowNonAdmin) + { + // + // SDDL_DEVOBJ_SYS_ALL_WORLD_RWX_RES_RWX allows the kernel and system complete + // control over the device. By default the admin can access the entire device, + // but cannot change the ACL (the admin must take control of the device first) + // + // Everyone else, including "restricted" or "untrusted" code can read or write + // to the device. Traversal beneath the device is also granted (removing it + // would only effect storage devices, except if the "bypass-traversal" + // privilege was revoked). + // + deviceAttribute.DefaultSDDLString = &SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RWX_RES_RWX; + } +#endif + + status = NdisRegisterDeviceEx( + Adapter->MiniportAdapterHandle, + &deviceAttribute, + &Adapter->DeviceObject, + &Adapter->DeviceHandle + ); + } + + ASSERT(NT_SUCCESS(status)); + + if (NT_SUCCESS(status)) + { + // Set TAP device flags. + (Adapter->DeviceObject)->Flags &= ~DO_BUFFERED_IO; + (Adapter->DeviceObject)->Flags |= DO_DIRECT_IO;; + + //======================== + // Finalize initialization + //======================== + + Adapter->TapDeviceCreated = TRUE; + + DEBUGP (("[%wZ] successfully created TAP device [%wZ]\n", + &Adapter->NetCfgInstanceId, + &Adapter->DeviceName + )); + } + + DEBUGP (("[TAP] <-- CreateTapDevice; status = %8.8X\n",status)); + + return status; +} + +// +// DestroyTapDevice is called from AdapterHalt and NDIS miniport +// is in Halted state. Prior to entering the Halted state the +// miniport would have passed through the Pausing and Paused +// states. These miniport states have responsibility for waiting +// until NDIS network operations have completed. +// +VOID +DestroyTapDevice( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + DEBUGP (("[TAP] --> DestroyTapDevice; Adapter: %wZ\n", + &Adapter->NetCfgInstanceId)); + + // + // Let clients know we are shutting down + // + Adapter->TapDeviceCreated = FALSE; + + // + // Flush pending send TAP packet queue. + // + tapFlushSendPacketQueue(Adapter); + + ASSERT(Adapter->SendPacketQueue.Count == 0); + + // + // Flush IRP queues. Wait for pending I/O. Etc. + // -------------------------------------------- + // Exhaust IRP and packet queues. Any pending IRPs will + // be cancelled, causing user-space to get this error + // on overlapped reads: + // + // ERROR_OPERATION_ABORTED, code=995 + // + // "The I/O operation has been aborted because of either a + // thread exit or an application request." + // + // It's important that user-space close the device handle + // when this code is returned, so that when we finally + // do a NdisMDeregisterDeviceEx, the device reference count + // is 0. Otherwise the driver will not unload even if the + // the last adapter has been halted. + // + // The act of flushing the queues at this point should result in the user-mode + // application closing the adapter's device handle. Closing the handle will + // result in the TapDeviceCleanup call being made, followed by the a call to + // the TapDeviceClose callback. + // + tapFlushIrpQueues(Adapter); + + ASSERT(Adapter->PendingReadIrpQueue.Count == 0); + + // + // Deregister the Win32 device. + // ---------------------------- + // When a driver calls NdisDeregisterDeviceEx, the I/O manager deletes the + // target device object if there are no outstanding references to it. However, + // if any outstanding references remain, the I/O manager marks the device + // object as "delete pending" and deletes the device object when the references + // are finally released. + // + if(Adapter->DeviceHandle) + { + DEBUGP (("[TAP] Calling NdisDeregisterDeviceEx\n")); + NdisDeregisterDeviceEx(Adapter->DeviceHandle); + } + + Adapter->DeviceHandle = NULL; + + DEBUGP (("[TAP] <-- DestroyTapDevice\n")); +} + diff --git a/windows/TapDriver6/device.h b/windows/TapDriver6/device.h new file mode 100644 index 0000000..93dae0d --- /dev/null +++ b/windows/TapDriver6/device.h @@ -0,0 +1,50 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __TAP_DEVICE_H_ +#define __TAP_DEVICE_H_ + +//====================================================================== +// TAP Prototypes for standard Win32 device I/O entry points +//====================================================================== + +__drv_dispatchType(IRP_MJ_CREATE) +DRIVER_DISPATCH TapDeviceCreate; + +__drv_dispatchType(IRP_MJ_READ) +DRIVER_DISPATCH TapDeviceRead; + +__drv_dispatchType(IRP_MJ_WRITE) +DRIVER_DISPATCH TapDeviceWrite; + +__drv_dispatchType(IRP_MJ_DEVICE_CONTROL) +DRIVER_DISPATCH TapDeviceControl; + +__drv_dispatchType(IRP_MJ_CLEANUP) +DRIVER_DISPATCH TapDeviceCleanup; + +__drv_dispatchType(IRP_MJ_CLOSE) +DRIVER_DISPATCH TapDeviceClose; + +#endif // __TAP_DEVICE_H_ \ No newline at end of file diff --git a/windows/TapDriver6/endian.h b/windows/TapDriver6/endian.h new file mode 100644 index 0000000..b7d3449 --- /dev/null +++ b/windows/TapDriver6/endian.h @@ -0,0 +1,35 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef TAP_LITTLE_ENDIAN +#define ntohs(x) RtlUshortByteSwap(x) +#define htons(x) RtlUshortByteSwap(x) +#define ntohl(x) RtlUlongByteSwap(x) +#define htonl(x) RtlUlongByteSwap(x) +#else +#define ntohs(x) ((USHORT)(x)) +#define htons(x) ((USHORT)(x)) +#define ntohl(x) ((ULONG)(x)) +#define htonl(x) ((ULONG)(x)) +#endif diff --git a/windows/TapDriver6/error.c b/windows/TapDriver6/error.c new file mode 100644 index 0000000..1fad1d3 --- /dev/null +++ b/windows/TapDriver6/error.c @@ -0,0 +1,398 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "tap.h" + +//----------------- +// DEBUGGING OUTPUT +//----------------- + +const char *g_LastErrorFilename; +int g_LastErrorLineNumber; + +#if DBG + +DebugOutput g_Debug; + +BOOLEAN +NewlineExists (const char *str, int len) +{ + while (len-- > 0) + { + const char c = *str++; + if (c == '\n') + return TRUE; + else if (c == '\0') + break; + } + return FALSE; +} + +VOID +MyDebugInit (unsigned int bufsiz) +{ + NdisZeroMemory (&g_Debug, sizeof (g_Debug)); + g_Debug.text = (char *) MemAlloc (bufsiz, FALSE); + + if (g_Debug.text) + { + g_Debug.capacity = bufsiz; + } +} + +VOID +MyDebugFree () +{ + if (g_Debug.text) + { + MemFree (g_Debug.text, g_Debug.capacity); + } + + NdisZeroMemory (&g_Debug, sizeof (g_Debug)); +} + +VOID +MyDebugPrint (const unsigned char* format, ...) +{ + if (g_Debug.text && g_Debug.capacity > 0 && CAN_WE_PRINT) + { + BOOLEAN owned; + ACQUIRE_MUTEX_ADAPTIVE (&g_Debug.lock, owned); + if (owned) + { + const int remaining = (int)g_Debug.capacity - (int)g_Debug.out; + + if (remaining > 0) + { + va_list args; + NTSTATUS status; + char *end; + +#ifdef DBG_PRINT + va_start (args, format); + vDbgPrintEx (DPFLTR_IHVNETWORK_ID, DPFLTR_INFO_LEVEL, format, args); + va_end (args); +#endif + va_start (args, format); + status = RtlStringCchVPrintfExA (g_Debug.text + g_Debug.out, + remaining, + &end, + NULL, + STRSAFE_NO_TRUNCATION | STRSAFE_IGNORE_NULLS, + format, + args); + va_end (args); + va_start (args, format); + vDbgPrintEx(DPFLTR_IHVDRIVER_ID , 1, format, args); + va_end (args); + if (status == STATUS_SUCCESS) + g_Debug.out = (unsigned int) (end - g_Debug.text); + else + g_Debug.error = TRUE; + } + else + g_Debug.error = TRUE; + + RELEASE_MUTEX (&g_Debug.lock); + } + else + g_Debug.error = TRUE; + } +} + +BOOLEAN +GetDebugLine ( + __in char *buf, + __in const int len + ) +{ + static const char *truncated = "[OUTPUT TRUNCATED]\n"; + BOOLEAN ret = FALSE; + + NdisZeroMemory (buf, len); + + if (g_Debug.text && g_Debug.capacity > 0) + { + BOOLEAN owned; + ACQUIRE_MUTEX_ADAPTIVE (&g_Debug.lock, owned); + if (owned) + { + int i = 0; + + if (g_Debug.error || NewlineExists (g_Debug.text + g_Debug.in, (int)g_Debug.out - (int)g_Debug.in)) + { + while (i < (len - 1) && g_Debug.in < g_Debug.out) + { + const char c = g_Debug.text[g_Debug.in++]; + if (c == '\n') + break; + buf[i++] = c; + } + if (i < len) + buf[i] = '\0'; + } + + if (!i) + { + if (g_Debug.in == g_Debug.out) + { + g_Debug.in = g_Debug.out = 0; + if (g_Debug.error) + { + const unsigned int tlen = strlen (truncated); + if (tlen < g_Debug.capacity) + { + NdisMoveMemory (g_Debug.text, truncated, tlen+1); + g_Debug.out = tlen; + } + g_Debug.error = FALSE; + } + } + } + else + ret = TRUE; + + RELEASE_MUTEX (&g_Debug.lock); + } + } + return ret; +} + +VOID +PrMac (const MACADDR mac) +{ + DEBUGP (("%x:%x:%x:%x:%x:%x", + mac[0], mac[1], mac[2], + mac[3], mac[4], mac[5])); +} + +VOID +PrIP (IPADDR ip_addr) +{ + const unsigned char *ip = (const unsigned char *) &ip_addr; + + DEBUGP (("%d.%d.%d.%d", + ip[0], ip[1], ip[2], ip[3])); +} + +const char * +PrIPProto (int proto) +{ + switch (proto) + { + case IPPROTO_UDP: + return "UDP"; + + case IPPROTO_TCP: + return "TCP"; + + case IPPROTO_ICMP: + return "ICMP"; + + case IPPROTO_IGMP: + return "IGMP"; + + default: + return "???"; + } +} + +VOID +DumpARP (const char *prefix, const ARP_PACKET *arp) +{ + DEBUGP (("%s ARP src=", prefix)); + PrMac (arp->m_MAC_Source); + DEBUGP ((" dest=")); + PrMac (arp->m_MAC_Destination); + DEBUGP ((" OP=0x%04x", + (int)ntohs(arp->m_ARP_Operation))); + DEBUGP ((" M=0x%04x(%d)", + (int)ntohs(arp->m_MAC_AddressType), + (int)arp->m_MAC_AddressSize)); + DEBUGP ((" P=0x%04x(%d)", + (int)ntohs(arp->m_PROTO_AddressType), + (int)arp->m_PROTO_AddressSize)); + + DEBUGP ((" MacSrc=")); + PrMac (arp->m_ARP_MAC_Source); + DEBUGP ((" MacDest=")); + PrMac (arp->m_ARP_MAC_Destination); + + DEBUGP ((" IPSrc=")); + PrIP (arp->m_ARP_IP_Source); + DEBUGP ((" IPDest=")); + PrIP (arp->m_ARP_IP_Destination); + + DEBUGP (("\n")); +} + +struct ethpayload +{ + ETH_HEADER eth; + UCHAR payload[DEFAULT_PACKET_LOOKAHEAD]; +}; + +#ifdef ALLOW_PACKET_DUMP + +VOID +DumpPacket2( + __in const char *prefix, + __in const ETH_HEADER *eth, + __in const unsigned char *data, + __in unsigned int len + ) +{ + struct ethpayload *ep = (struct ethpayload *) MemAlloc (sizeof (struct ethpayload), TRUE); + if (ep) + { + if (len > DEFAULT_PACKET_LOOKAHEAD) + len = DEFAULT_PACKET_LOOKAHEAD; + ep->eth = *eth; + NdisMoveMemory (ep->payload, data, len); + DumpPacket (prefix, (unsigned char *) ep, sizeof (ETH_HEADER) + len); + MemFree (ep, sizeof (struct ethpayload)); + } +} + +VOID +DumpPacket( + __in const char *prefix, + __in const unsigned char *data, + __in unsigned int len + ) +{ + const ETH_HEADER *eth = (const ETH_HEADER *) data; + const IPHDR *ip = (const IPHDR *) (data + sizeof (ETH_HEADER)); + + if (len < sizeof (ETH_HEADER)) + { + DEBUGP (("%s TRUNCATED PACKET LEN=%d\n", prefix, len)); + return; + } + + // ARP Packet? + if (len >= sizeof (ARP_PACKET) && eth->proto == htons (ETH_P_ARP)) + { + DumpARP (prefix, (const ARP_PACKET *) data); + return; + } + + // IPv4 packet? + if (len >= (sizeof (IPHDR) + sizeof (ETH_HEADER)) + && eth->proto == htons (ETH_P_IP) + && IPH_GET_VER (ip->version_len) == 4) + { + const int hlen = IPH_GET_LEN (ip->version_len); + const int blen = len - sizeof (ETH_HEADER); + BOOLEAN did = FALSE; + + DEBUGP (("%s IPv4 %s[%d]", prefix, PrIPProto (ip->protocol), len)); + + if (!(ntohs (ip->tot_len) == blen && hlen <= blen)) + { + DEBUGP ((" XXX")); + return; + } + + // TCP packet? + if (ip->protocol == IPPROTO_TCP + && blen - hlen >= (sizeof (TCPHDR))) + { + const TCPHDR *tcp = (TCPHDR *) (data + sizeof (ETH_HEADER) + hlen); + DEBUGP ((" ")); + PrIP (ip->saddr); + DEBUGP ((":%d", ntohs (tcp->source))); + DEBUGP ((" -> ")); + PrIP (ip->daddr); + DEBUGP ((":%d", ntohs (tcp->dest))); + did = TRUE; + } + + // UDP packet? + else if ((ntohs (ip->frag_off) & IP_OFFMASK) == 0 + && ip->protocol == IPPROTO_UDP + && blen - hlen >= (sizeof (UDPHDR))) + { + const UDPHDR *udp = (UDPHDR *) (data + sizeof (ETH_HEADER) + hlen); + + // DHCP packet? + if ((udp->dest == htons (BOOTPC_PORT) || udp->dest == htons (BOOTPS_PORT)) + && blen - hlen >= (sizeof (UDPHDR) + sizeof (DHCP))) + { + const DHCP *dhcp = (DHCP *) (data + + hlen + + sizeof (ETH_HEADER) + + sizeof (UDPHDR)); + + int optlen = len + - sizeof (ETH_HEADER) + - hlen + - sizeof (UDPHDR) + - sizeof (DHCP); + + if (optlen < 0) + optlen = 0; + + DumpDHCP (eth, ip, udp, dhcp, optlen); + did = TRUE; + } + + if (!did) + { + DEBUGP ((" ")); + PrIP (ip->saddr); + DEBUGP ((":%d", ntohs (udp->source))); + DEBUGP ((" -> ")); + PrIP (ip->daddr); + DEBUGP ((":%d", ntohs (udp->dest))); + did = TRUE; + } + } + + if (!did) + { + DEBUGP ((" ipproto=%d ", ip->protocol)); + PrIP (ip->saddr); + DEBUGP ((" -> ")); + PrIP (ip->daddr); + } + + DEBUGP (("\n")); + return; + } + + { + DEBUGP (("%s ??? src=", prefix)); + PrMac (eth->src); + DEBUGP ((" dest=")); + PrMac (eth->dest); + DEBUGP ((" proto=0x%04x len=%d\n", + (int) ntohs(eth->proto), + len)); + } +} + +#endif // ALLOW_PACKET_DUMP + +#endif diff --git a/windows/TapDriver6/error.h b/windows/TapDriver6/error.h new file mode 100644 index 0000000..2ba39cc --- /dev/null +++ b/windows/TapDriver6/error.h @@ -0,0 +1,114 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +//----------------- +// DEBUGGING OUTPUT +//----------------- + +extern const char *g_LastErrorFilename; +extern int g_LastErrorLineNumber; + +// Debug info output +#define ALSO_DBGPRINT 1 +#define DEBUGP_AT_DISPATCH 1 + +// Uncomment line below to allow packet dumps +//#define ALLOW_PACKET_DUMP 1 + +#define NOTE_ERROR() \ +{ \ + g_LastErrorFilename = __FILE__; \ + g_LastErrorLineNumber = __LINE__; \ +} + +#if DBG + +typedef struct +{ + unsigned int in; + unsigned int out; + unsigned int capacity; + char *text; + BOOLEAN error; + MUTEX lock; +} DebugOutput; + +VOID MyDebugPrint (const unsigned char* format, ...); + +VOID PrMac (const MACADDR mac); + +VOID PrIP (IPADDR ip_addr); + +#ifdef ALLOW_PACKET_DUMP + +VOID +DumpPacket( + __in const char *prefix, + __in const unsigned char *data, + __in unsigned int len + ); + +DumpPacket2( + __in const char *prefix, + __in const ETH_HEADER *eth, + __in const unsigned char *data, + __in unsigned int len + ); + +#else +#define DUMP_PACKET(prefix, data, len) +#define DUMP_PACKET2(prefix, eth, data, len) +#endif + +#define CAN_WE_PRINT (DEBUGP_AT_DISPATCH || KeGetCurrentIrql () < DISPATCH_LEVEL) + +#if ALSO_DBGPRINT +#define DEBUGP(fmt) { MyDebugPrint fmt; if (CAN_WE_PRINT) DbgPrint fmt; } +#else +#define DEBUGP(fmt) { MyDebugPrint fmt; } +#endif + +#ifdef ALLOW_PACKET_DUMP + +#define DUMP_PACKET(prefix, data, len) \ + DumpPacket (prefix, data, len) + +#define DUMP_PACKET2(prefix, eth, data, len) \ + DumpPacket2 (prefix, eth, data, len) + +#endif + +BOOLEAN +GetDebugLine ( + __in char *buf, + __in const int len + ); + +#else + +#define DEBUGP(fmt) +#define DUMP_PACKET(prefix, data, len) +#define DUMP_PACKET2(prefix, eth, data, len) + +#endif diff --git a/windows/TapDriver6/hexdump.h b/windows/TapDriver6/hexdump.h new file mode 100644 index 0000000..d6275c1 --- /dev/null +++ b/windows/TapDriver6/hexdump.h @@ -0,0 +1,63 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef HEXDUMP_DEFINED +#define HEXDUMP_DEFINED + +#ifdef __cplusplus +extern "C" { +#endif + +//===================================================================================== +// Debug Routines +//===================================================================================== + +#ifndef NDIS_MINIPORT_DRIVER +# include +# include +# include +# include +# include + +# ifndef DEBUGP +# define DEBUGP(fmt) { DbgMessage fmt; } +# endif + + extern VOID (*DbgMessage)(char *p_Format, ...); + + VOID DisplayDebugString (char *p_Format, ...); +#endif + +//=================================================================================== +// Reporting / Debugging +//=================================================================================== +#define IfPrint(c) (c >= 32 && c < 127 ? c : '.') + +VOID HexDump (unsigned char *p_Buffer, unsigned long p_Size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/windows/TapDriver6/lock.h b/windows/TapDriver6/lock.h new file mode 100644 index 0000000..c80b164 --- /dev/null +++ b/windows/TapDriver6/lock.h @@ -0,0 +1,75 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +typedef struct +{ + volatile long count; +} MUTEX; + +#define MUTEX_SLEEP_TIME 10000 // microseconds + +#define INIT_MUTEX(m) { (m)->count = 0; } + +#define ACQUIRE_MUTEX_BLOCKING(m) \ +{ \ + while (NdisInterlockedIncrement (&((m)->count)) != 1) \ + { \ + NdisInterlockedDecrement(&((m)->count)); \ + NdisMSleep(MUTEX_SLEEP_TIME); \ + } \ +} + +#define RELEASE_MUTEX(m) \ +{ \ + NdisInterlockedDecrement(&((m)->count)); \ +} + +#define ACQUIRE_MUTEX_NONBLOCKING(m, result) \ +{ \ + if (NdisInterlockedIncrement (&((m)->count)) != 1) \ + { \ + NdisInterlockedDecrement(&((m)->count)); \ + result = FALSE; \ + } \ + else \ + { \ + result = TRUE; \ + } \ +} + +#define ACQUIRE_MUTEX_ADAPTIVE(m, result) \ +{ \ + result = TRUE; \ + while (NdisInterlockedIncrement (&((m)->count)) != 1) \ + { \ + NdisInterlockedDecrement(&((m)->count)); \ + if (KeGetCurrentIrql () < DISPATCH_LEVEL) \ + NdisMSleep(MUTEX_SLEEP_TIME); \ + else \ + { \ + result = FALSE; \ + break; \ + } \ + } \ +} diff --git a/windows/TapDriver6/macinfo.c b/windows/TapDriver6/macinfo.c new file mode 100644 index 0000000..dfd0a07 --- /dev/null +++ b/windows/TapDriver6/macinfo.c @@ -0,0 +1,164 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "tap.h" + +int +HexStringToDecimalInt (const int p_Character) +{ + int l_Value = 0; + + if (p_Character >= 'A' && p_Character <= 'F') + l_Value = (p_Character - 'A') + 10; + else if (p_Character >= 'a' && p_Character <= 'f') + l_Value = (p_Character - 'a') + 10; + else if (p_Character >= '0' && p_Character <= '9') + l_Value = p_Character - '0'; + + return l_Value; +} + +BOOLEAN +ParseMAC (MACADDR dest, const char *src) +{ + int c; + int mac_index = 0; + BOOLEAN high_digit = FALSE; + int delim_action = 1; + + ASSERT (src); + ASSERT (dest); + + CLEAR_MAC (dest); + + while (c = *src++) + { + if (IsMacDelimiter (c)) + { + mac_index += delim_action; + high_digit = FALSE; + delim_action = 1; + } + else if (IsHexDigit (c)) + { + const int digit = HexStringToDecimalInt (c); + if (mac_index < sizeof (MACADDR)) + { + if (!high_digit) + { + dest[mac_index] = (char)(digit); + high_digit = TRUE; + delim_action = 1; + } + else + { + dest[mac_index] = (char)(dest[mac_index] * 16 + digit); + ++mac_index; + high_digit = FALSE; + delim_action = 0; + } + } + else + return FALSE; + } + else + return FALSE; + } + + return (mac_index + delim_action) >= sizeof (MACADDR); +} + +/* + * Generate a MAC using the GUID in the adapter name. + * + * The mac is constructed as 00:FF:xx:xx:xx:xx where + * the Xs are taken from the first 32 bits of the GUID in the + * adapter name. This is similar to the Linux 2.4 tap MAC + * generator, except linux uses 32 random bits for the Xs. + * + * In general, this solution is reasonable for most + * applications except for very large bridged TAP networks, + * where the probability of address collisions becomes more + * than infintesimal. + * + * Using the well-known "birthday paradox", on a 1000 node + * network the probability of collision would be + * 0.000116292153. On a 10,000 node network, the probability + * of collision would be 0.01157288998621678766. + */ + +VOID +GenerateRandomMac( + __in MACADDR mac, + __in const unsigned char *adapter_name + ) +{ + unsigned const char *cp = adapter_name; + unsigned char c; + unsigned int i = 2; + unsigned int byte = 0; + int brace = 0; + int state = 0; + + CLEAR_MAC (mac); + + mac[0] = 0x00; + mac[1] = 0xFF; + + while (c = *cp++) + { + if (i >= sizeof (MACADDR)) + break; + if (c == '{') + brace = 1; + if (IsHexDigit (c) && brace) + { + const unsigned int digit = HexStringToDecimalInt (c); + if (state) + { + byte <<= 4; + byte |= digit; + mac[i++] = (unsigned char) byte; + state = 0; + } + else + { + byte = digit; + state = 1; + } + } + } +} + +VOID +GenerateRelatedMAC( + __in MACADDR dest, + __in const MACADDR src, + __in const int delta + ) +{ + ETH_COPY_NETWORK_ADDRESS (dest, src); + dest[2] += (UCHAR) delta; +} diff --git a/windows/TapDriver6/macinfo.h b/windows/TapDriver6/macinfo.h new file mode 100644 index 0000000..dd88b6f --- /dev/null +++ b/windows/TapDriver6/macinfo.h @@ -0,0 +1,53 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MacInfoDefined +#define MacInfoDefined + +//=================================================================================== +// Macros +//=================================================================================== +#define IsMacDelimiter(a) (a == ':' || a == '-' || a == '.') +#define IsHexDigit(c) ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) + +#define CLEAR_MAC(dest) NdisZeroMemory ((dest), sizeof (MACADDR)) +#define MAC_EQUAL(a,b) (memcmp ((a), (b), sizeof (MACADDR)) == 0) + +BOOLEAN +ParseMAC (MACADDR dest, const char *src); + +VOID +GenerateRandomMac( + __in MACADDR mac, + __in const unsigned char *adapter_name + ); + +VOID +GenerateRelatedMAC( + __in MACADDR dest, + __in const MACADDR src, + __in const int delta + ); + +#endif diff --git a/windows/TapDriver6/mem.c b/windows/TapDriver6/mem.c new file mode 100644 index 0000000..ae2e3d4 --- /dev/null +++ b/windows/TapDriver6/mem.c @@ -0,0 +1,401 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +//------------------ +// Memory Management +//------------------ + +#include "tap.h" + +PVOID +MemAlloc( + __in ULONG p_Size, + __in BOOLEAN zero + ) +{ + PVOID l_Return = NULL; + + if (p_Size) + { + __try + { + if (NdisAllocateMemoryWithTag (&l_Return, p_Size, 'APAT') + == NDIS_STATUS_SUCCESS) + { + if (zero) + { + NdisZeroMemory (l_Return, p_Size); + } + } + else + { + l_Return = NULL; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + l_Return = NULL; + } + } + + return l_Return; +} + +VOID +MemFree( + __in PVOID p_Addr, + __in ULONG p_Size + ) +{ + if (p_Addr && p_Size) + { + __try + { +#if DBG + NdisZeroMemory (p_Addr, p_Size); +#endif + NdisFreeMemory (p_Addr, p_Size, 0); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + } + } +} + +//====================================================================== +// TAP Packet Queue Support +//====================================================================== + +VOID +tapPacketQueueInsertTail( + __in PTAP_PACKET_QUEUE TapPacketQueue, + __in PTAP_PACKET TapPacket + ) +{ + KIRQL irql; + + KeAcquireSpinLock(&TapPacketQueue->QueueLock,&irql); + + InsertTailList(&TapPacketQueue->Queue,&TapPacket->QueueLink); + + // BUGBUG!!! Enforce PACKET_QUEUE_SIZE queue count limit??? + // For NDIS 6 there is no per-packet status, so this will need to + // be handled on per-NBL basis in AdapterSendNetBufferLists... + + // Update counts + ++TapPacketQueue->Count; + + if(TapPacketQueue->Count > TapPacketQueue->MaxCount) + { + TapPacketQueue->MaxCount = TapPacketQueue->Count; + + DEBUGP (("[TAP] tapPacketQueueInsertTail: New MAX queued packet count = %d\n", + TapPacketQueue->MaxCount)); + } + + KeReleaseSpinLock(&TapPacketQueue->QueueLock,irql); +} + +// Call with QueueLock held +PTAP_PACKET +tapPacketRemoveHeadLocked( + __in PTAP_PACKET_QUEUE TapPacketQueue + ) +{ + PTAP_PACKET tapPacket = NULL; + PLIST_ENTRY listEntry; + + listEntry = RemoveHeadList(&TapPacketQueue->Queue); + + if(listEntry != &TapPacketQueue->Queue) + { + tapPacket = CONTAINING_RECORD(listEntry, TAP_PACKET, QueueLink); + + // Update counts + --TapPacketQueue->Count; + } + + return tapPacket; +} + +PTAP_PACKET +tapPacketRemoveHead( + __in PTAP_PACKET_QUEUE TapPacketQueue + ) +{ + PTAP_PACKET tapPacket = NULL; + KIRQL irql; + + KeAcquireSpinLock(&TapPacketQueue->QueueLock,&irql); + + tapPacket = tapPacketRemoveHeadLocked(TapPacketQueue); + + KeReleaseSpinLock(&TapPacketQueue->QueueLock,irql); + + return tapPacket; +} + +VOID +tapPacketQueueInitialize( + __in PTAP_PACKET_QUEUE TapPacketQueue + ) +{ + KeInitializeSpinLock(&TapPacketQueue->QueueLock); + + NdisInitializeListHead(&TapPacketQueue->Queue); +} + +//====================================================================== +// TAP Cancel-Safe Queue Support +//====================================================================== + +VOID +tapIrpCsqInsert ( + __in struct _IO_CSQ *Csq, + __in PIRP Irp + ) +{ + PTAP_IRP_CSQ tapIrpCsq; + + tapIrpCsq = (PTAP_IRP_CSQ )Csq; + + InsertTailList( + &tapIrpCsq->Queue, + &Irp->Tail.Overlay.ListEntry + ); + + // Update counts + ++tapIrpCsq->Count; + + if(tapIrpCsq->Count > tapIrpCsq->MaxCount) + { + tapIrpCsq->MaxCount = tapIrpCsq->Count; + + DEBUGP (("[TAP] tapIrpCsqInsert: New MAX queued IRP count = %d\n", + tapIrpCsq->MaxCount)); + } +} + +VOID +tapIrpCsqRemoveIrp( + __in PIO_CSQ Csq, + __in PIRP Irp + ) +{ + PTAP_IRP_CSQ tapIrpCsq; + + tapIrpCsq = (PTAP_IRP_CSQ )Csq; + + // Update counts + --tapIrpCsq->Count; + + RemoveEntryList(&Irp->Tail.Overlay.ListEntry); +} + + +PIRP +tapIrpCsqPeekNextIrp( + __in PIO_CSQ Csq, + __in PIRP Irp, + __in PVOID PeekContext + ) +{ + PTAP_IRP_CSQ tapIrpCsq; + PIRP nextIrp = NULL; + PLIST_ENTRY nextEntry; + PLIST_ENTRY listHead; + PIO_STACK_LOCATION irpStack; + + tapIrpCsq = (PTAP_IRP_CSQ )Csq; + + listHead = &tapIrpCsq->Queue; + + // + // If the IRP is NULL, we will start peeking from the listhead, else + // we will start from that IRP onwards. This is done under the + // assumption that new IRPs are always inserted at the tail. + // + + if (Irp == NULL) + { + nextEntry = listHead->Flink; + } + else + { + nextEntry = Irp->Tail.Overlay.ListEntry.Flink; + } + + while(nextEntry != listHead) + { + nextIrp = CONTAINING_RECORD(nextEntry, IRP, Tail.Overlay.ListEntry); + + irpStack = IoGetCurrentIrpStackLocation(nextIrp); + + // + // If context is present, continue until you find a matching one. + // Else you break out as you got next one. + // + if (PeekContext) + { + if (irpStack->FileObject == (PFILE_OBJECT) PeekContext) + { + break; + } + } + else + { + break; + } + + nextIrp = NULL; + nextEntry = nextEntry->Flink; + } + + return nextIrp; +} + +// +// tapIrpCsqAcquireQueueLock modifies the execution level of the current processor. +// +// KeAcquireSpinLock raises the execution level to Dispatch Level and stores +// the current execution level in the Irql parameter to be restored at a later +// time. KeAcqurieSpinLock also requires us to be running at no higher than +// Dispatch level when it is called. +// +// The annotations reflect these changes and requirments. +// + +__drv_raisesIRQL(DISPATCH_LEVEL) +__drv_maxIRQL(DISPATCH_LEVEL) +VOID +tapIrpCsqAcquireQueueLock( + __in PIO_CSQ Csq, + __out PKIRQL Irql + ) +{ + PTAP_IRP_CSQ tapIrpCsq; + + tapIrpCsq = (PTAP_IRP_CSQ )Csq; + + // + // Suppressing because the address below csq is valid since it's + // part of TAP_ADAPTER_CONTEXT structure. + // +#pragma prefast(suppress: __WARNING_BUFFER_UNDERFLOW, "Underflow using expression 'adapter->PendingReadCsqQueueLock'") + KeAcquireSpinLock(&tapIrpCsq->QueueLock, Irql); +} + +// +// tapIrpCsqReleaseQueueLock modifies the execution level of the current processor. +// +// KeReleaseSpinLock assumes we already hold the spin lock and are therefore +// running at Dispatch level. It will use the Irql parameter saved in a +// previous call to KeAcquireSpinLock to return the thread back to it's original +// execution level. +// +// The annotations reflect these changes and requirments. +// + +__drv_requiresIRQL(DISPATCH_LEVEL) +VOID +tapIrpCsqReleaseQueueLock( + __in PIO_CSQ Csq, + __in KIRQL Irql + ) +{ + PTAP_IRP_CSQ tapIrpCsq; + + tapIrpCsq = (PTAP_IRP_CSQ )Csq; + + // + // Suppressing because the address below csq is valid since it's + // part of TAP_ADAPTER_CONTEXT structure. + // +#pragma prefast(suppress: __WARNING_BUFFER_UNDERFLOW, "Underflow using expression 'adapter->PendingReadCsqQueueLock'") + KeReleaseSpinLock(&tapIrpCsq->QueueLock, Irql); +} + +VOID +tapIrpCsqCompleteCanceledIrp( + __in PIO_CSQ pCsq, + __in PIRP Irp + ) +{ + UNREFERENCED_PARAMETER(pCsq); + + Irp->IoStatus.Status = STATUS_CANCELLED; + Irp->IoStatus.Information = 0; + IoCompleteRequest(Irp, IO_NO_INCREMENT); +} + +VOID +tapIrpCsqInitialize( + __in PTAP_IRP_CSQ TapIrpCsq + ) +{ + KeInitializeSpinLock(&TapIrpCsq->QueueLock); + + NdisInitializeListHead(&TapIrpCsq->Queue); + + IoCsqInitialize( + &TapIrpCsq->CsqQueue, + tapIrpCsqInsert, + tapIrpCsqRemoveIrp, + tapIrpCsqPeekNextIrp, + tapIrpCsqAcquireQueueLock, + tapIrpCsqReleaseQueueLock, + tapIrpCsqCompleteCanceledIrp + ); +} + +VOID +tapIrpCsqFlush( + __in PTAP_IRP_CSQ TapIrpCsq + ) +{ + PIRP pendingIrp; + + // + // Flush the pending read IRP queue. + // + pendingIrp = IoCsqRemoveNextIrp( + &TapIrpCsq->CsqQueue, + NULL + ); + + while(pendingIrp) + { + // Cancel the IRP + pendingIrp->IoStatus.Information = 0; + pendingIrp->IoStatus.Status = STATUS_CANCELLED; + IoCompleteRequest(pendingIrp, IO_NO_INCREMENT); + + pendingIrp = IoCsqRemoveNextIrp( + &TapIrpCsq->CsqQueue, + NULL + ); + } + + ASSERT(IsListEmpty(&TapIrpCsq->Queue)); +} diff --git a/windows/TapDriver6/mem.h b/windows/TapDriver6/mem.h new file mode 100644 index 0000000..a8359e1 --- /dev/null +++ b/windows/TapDriver6/mem.h @@ -0,0 +1,113 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +//------------------ +// Memory Management +//------------------ + +PVOID +MemAlloc( + __in ULONG p_Size, + __in BOOLEAN zero + ); + +VOID +MemFree( + __in PVOID p_Addr, + __in ULONG p_Size + ); + +//====================================================================== +// TAP Packet Queue +//====================================================================== + +typedef +struct _TAP_PACKET +{ + LIST_ENTRY QueueLink; + +# define TAP_PACKET_SIZE(data_size) (sizeof (TAP_PACKET) + (data_size)) +# define TP_TUN 0x80000000 +# define TP_SIZE_MASK (~TP_TUN) + ULONG m_SizeFlags; + + // m_Data must be the last struct member + UCHAR m_Data []; +} TAP_PACKET, *PTAP_PACKET; + +#define TAP_PACKET_TAG '6PAT' // "TAP6" + +typedef struct _TAP_PACKET_QUEUE +{ + KSPIN_LOCK QueueLock; + LIST_ENTRY Queue; + ULONG Count; // Count of currently queued items + ULONG MaxCount; +} TAP_PACKET_QUEUE, *PTAP_PACKET_QUEUE; + +VOID +tapPacketQueueInsertTail( + __in PTAP_PACKET_QUEUE TapPacketQueue, + __in PTAP_PACKET TapPacket + ); + + +// Call with QueueLock held +PTAP_PACKET +tapPacketRemoveHeadLocked( + __in PTAP_PACKET_QUEUE TapPacketQueue + ); + +PTAP_PACKET +tapPacketRemoveHead( + __in PTAP_PACKET_QUEUE TapPacketQueue + ); + +VOID +tapPacketQueueInitialize( + __in PTAP_PACKET_QUEUE TapPacketQueue + ); + +//---------------------- +// Cancel-Safe IRP Queue +//---------------------- + +typedef struct _TAP_IRP_CSQ +{ + IO_CSQ CsqQueue; + KSPIN_LOCK QueueLock; + LIST_ENTRY Queue; + ULONG Count; // Count of currently queued items + ULONG MaxCount; +} TAP_IRP_CSQ, *PTAP_IRP_CSQ; + +VOID +tapIrpCsqInitialize( + __in PTAP_IRP_CSQ TapIrpCsq + ); + +VOID +tapIrpCsqFlush( + __in PTAP_IRP_CSQ TapIrpCsq + ); diff --git a/windows/TapDriver6/oidrequest.c b/windows/TapDriver6/oidrequest.c new file mode 100644 index 0000000..a6882f8 --- /dev/null +++ b/windows/TapDriver6/oidrequest.c @@ -0,0 +1,1028 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// +// Include files. +// + +#include "tap.h" + +#ifndef DBG + +#define DBG_PRINT_OID_NAME + +#else + +VOID +DBG_PRINT_OID_NAME( + __in NDIS_OID Oid + ) +{ + PCHAR oidName = NULL; + + switch (Oid){ + + #undef MAKECASE + #define MAKECASE(oidx) case oidx: oidName = #oidx "\n"; break; + + /* Operational OIDs */ + MAKECASE(OID_GEN_SUPPORTED_LIST) + MAKECASE(OID_GEN_HARDWARE_STATUS) + MAKECASE(OID_GEN_MEDIA_SUPPORTED) + MAKECASE(OID_GEN_MEDIA_IN_USE) + MAKECASE(OID_GEN_MAXIMUM_LOOKAHEAD) + MAKECASE(OID_GEN_MAXIMUM_FRAME_SIZE) + MAKECASE(OID_GEN_LINK_SPEED) + MAKECASE(OID_GEN_TRANSMIT_BUFFER_SPACE) + MAKECASE(OID_GEN_RECEIVE_BUFFER_SPACE) + MAKECASE(OID_GEN_TRANSMIT_BLOCK_SIZE) + MAKECASE(OID_GEN_RECEIVE_BLOCK_SIZE) + MAKECASE(OID_GEN_VENDOR_ID) + MAKECASE(OID_GEN_VENDOR_DESCRIPTION) + MAKECASE(OID_GEN_VENDOR_DRIVER_VERSION) + MAKECASE(OID_GEN_CURRENT_PACKET_FILTER) + MAKECASE(OID_GEN_CURRENT_LOOKAHEAD) + MAKECASE(OID_GEN_DRIVER_VERSION) + MAKECASE(OID_GEN_MAXIMUM_TOTAL_SIZE) + MAKECASE(OID_GEN_PROTOCOL_OPTIONS) + MAKECASE(OID_GEN_MAC_OPTIONS) + MAKECASE(OID_GEN_MEDIA_CONNECT_STATUS) + MAKECASE(OID_GEN_MAXIMUM_SEND_PACKETS) + MAKECASE(OID_GEN_SUPPORTED_GUIDS) + MAKECASE(OID_GEN_NETWORK_LAYER_ADDRESSES) + MAKECASE(OID_GEN_TRANSPORT_HEADER_OFFSET) + MAKECASE(OID_GEN_MEDIA_CAPABILITIES) + MAKECASE(OID_GEN_PHYSICAL_MEDIUM) + MAKECASE(OID_GEN_MACHINE_NAME) + MAKECASE(OID_GEN_VLAN_ID) + MAKECASE(OID_GEN_RNDIS_CONFIG_PARAMETER) + + /* Operational OIDs for NDIS 6.0 */ + MAKECASE(OID_GEN_MAX_LINK_SPEED) + MAKECASE(OID_GEN_LINK_STATE) + MAKECASE(OID_GEN_LINK_PARAMETERS) + MAKECASE(OID_GEN_MINIPORT_RESTART_ATTRIBUTES) + MAKECASE(OID_GEN_ENUMERATE_PORTS) + MAKECASE(OID_GEN_PORT_STATE) + MAKECASE(OID_GEN_PORT_AUTHENTICATION_PARAMETERS) + MAKECASE(OID_GEN_INTERRUPT_MODERATION) + MAKECASE(OID_GEN_PHYSICAL_MEDIUM_EX) + + /* Statistical OIDs */ + MAKECASE(OID_GEN_XMIT_OK) + MAKECASE(OID_GEN_RCV_OK) + MAKECASE(OID_GEN_XMIT_ERROR) + MAKECASE(OID_GEN_RCV_ERROR) + MAKECASE(OID_GEN_RCV_NO_BUFFER) + MAKECASE(OID_GEN_DIRECTED_BYTES_XMIT) + MAKECASE(OID_GEN_DIRECTED_FRAMES_XMIT) + MAKECASE(OID_GEN_MULTICAST_BYTES_XMIT) + MAKECASE(OID_GEN_MULTICAST_FRAMES_XMIT) + MAKECASE(OID_GEN_BROADCAST_BYTES_XMIT) + MAKECASE(OID_GEN_BROADCAST_FRAMES_XMIT) + MAKECASE(OID_GEN_DIRECTED_BYTES_RCV) + MAKECASE(OID_GEN_DIRECTED_FRAMES_RCV) + MAKECASE(OID_GEN_MULTICAST_BYTES_RCV) + MAKECASE(OID_GEN_MULTICAST_FRAMES_RCV) + MAKECASE(OID_GEN_BROADCAST_BYTES_RCV) + MAKECASE(OID_GEN_BROADCAST_FRAMES_RCV) + MAKECASE(OID_GEN_RCV_CRC_ERROR) + MAKECASE(OID_GEN_TRANSMIT_QUEUE_LENGTH) + + /* Statistical OIDs for NDIS 6.0 */ + MAKECASE(OID_GEN_STATISTICS) + MAKECASE(OID_GEN_BYTES_RCV) + MAKECASE(OID_GEN_BYTES_XMIT) + MAKECASE(OID_GEN_RCV_DISCARDS) + MAKECASE(OID_GEN_XMIT_DISCARDS) + + /* Misc OIDs */ + MAKECASE(OID_GEN_GET_TIME_CAPS) + MAKECASE(OID_GEN_GET_NETCARD_TIME) + MAKECASE(OID_GEN_NETCARD_LOAD) + MAKECASE(OID_GEN_DEVICE_PROFILE) + MAKECASE(OID_GEN_INIT_TIME_MS) + MAKECASE(OID_GEN_RESET_COUNTS) + MAKECASE(OID_GEN_MEDIA_SENSE_COUNTS) + + /* PnP power management operational OIDs */ + MAKECASE(OID_PNP_CAPABILITIES) + MAKECASE(OID_PNP_SET_POWER) + MAKECASE(OID_PNP_QUERY_POWER) + MAKECASE(OID_PNP_ADD_WAKE_UP_PATTERN) + MAKECASE(OID_PNP_REMOVE_WAKE_UP_PATTERN) + MAKECASE(OID_PNP_ENABLE_WAKE_UP) + MAKECASE(OID_PNP_WAKE_UP_PATTERN_LIST) + + /* PnP power management statistical OIDs */ + MAKECASE(OID_PNP_WAKE_UP_ERROR) + MAKECASE(OID_PNP_WAKE_UP_OK) + + /* Ethernet operational OIDs */ + MAKECASE(OID_802_3_PERMANENT_ADDRESS) + MAKECASE(OID_802_3_CURRENT_ADDRESS) + MAKECASE(OID_802_3_MULTICAST_LIST) + MAKECASE(OID_802_3_MAXIMUM_LIST_SIZE) + MAKECASE(OID_802_3_MAC_OPTIONS) + + /* Ethernet operational OIDs for NDIS 6.0 */ + MAKECASE(OID_802_3_ADD_MULTICAST_ADDRESS) + MAKECASE(OID_802_3_DELETE_MULTICAST_ADDRESS) + + /* Ethernet statistical OIDs */ + MAKECASE(OID_802_3_RCV_ERROR_ALIGNMENT) + MAKECASE(OID_802_3_XMIT_ONE_COLLISION) + MAKECASE(OID_802_3_XMIT_MORE_COLLISIONS) + MAKECASE(OID_802_3_XMIT_DEFERRED) + MAKECASE(OID_802_3_XMIT_MAX_COLLISIONS) + MAKECASE(OID_802_3_RCV_OVERRUN) + MAKECASE(OID_802_3_XMIT_UNDERRUN) + MAKECASE(OID_802_3_XMIT_HEARTBEAT_FAILURE) + MAKECASE(OID_802_3_XMIT_TIMES_CRS_LOST) + MAKECASE(OID_802_3_XMIT_LATE_COLLISIONS) + + /* TCP/IP OIDs */ + MAKECASE(OID_TCP_TASK_OFFLOAD) + MAKECASE(OID_TCP_TASK_IPSEC_ADD_SA) + MAKECASE(OID_TCP_TASK_IPSEC_DELETE_SA) + MAKECASE(OID_TCP_SAN_SUPPORT) + MAKECASE(OID_TCP_TASK_IPSEC_ADD_UDPESP_SA) + MAKECASE(OID_TCP_TASK_IPSEC_DELETE_UDPESP_SA) + MAKECASE(OID_TCP4_OFFLOAD_STATS) + MAKECASE(OID_TCP6_OFFLOAD_STATS) + MAKECASE(OID_IP4_OFFLOAD_STATS) + MAKECASE(OID_IP6_OFFLOAD_STATS) + + /* TCP offload OIDs for NDIS 6 */ + MAKECASE(OID_TCP_OFFLOAD_CURRENT_CONFIG) + MAKECASE(OID_TCP_OFFLOAD_PARAMETERS) + MAKECASE(OID_TCP_OFFLOAD_HARDWARE_CAPABILITIES) + MAKECASE(OID_TCP_CONNECTION_OFFLOAD_CURRENT_CONFIG) + MAKECASE(OID_TCP_CONNECTION_OFFLOAD_HARDWARE_CAPABILITIES) + MAKECASE(OID_OFFLOAD_ENCAPSULATION) + +#if (NDIS_SUPPORT_NDIS620) + /* VMQ OIDs for NDIS 6.20 */ + MAKECASE(OID_RECEIVE_FILTER_FREE_QUEUE) + MAKECASE(OID_RECEIVE_FILTER_CLEAR_FILTER) + MAKECASE(OID_RECEIVE_FILTER_ALLOCATE_QUEUE) + MAKECASE(OID_RECEIVE_FILTER_QUEUE_ALLOCATION_COMPLETE) + MAKECASE(OID_RECEIVE_FILTER_SET_FILTER) +#endif + +#if (NDIS_SUPPORT_NDIS630) + /* NDIS QoS OIDs for NDIS 6.30 */ + MAKECASE(OID_QOS_PARAMETERS) +#endif + } + + if (oidName) + { + DEBUGP(("OID: %s", oidName)); + } + else + { + DEBUGP(("<** Unknown OID 0x%08x **>\n", Oid)); + } +} + +#endif // DBG + +//====================================================================== +// TAP NDIS 6 OID Request Callbacks +//====================================================================== + +NDIS_STATUS +tapSetMulticastList( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in PNDIS_OID_REQUEST OidRequest + ) +{ + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + + // + // Initialize. + // + OidRequest->DATA.SET_INFORMATION.BytesNeeded = MACADDR_SIZE; + OidRequest->DATA.SET_INFORMATION.BytesRead + = OidRequest->DATA.SET_INFORMATION.InformationBufferLength; + + + do + { + if (OidRequest->DATA.SET_INFORMATION.InformationBufferLength % MACADDR_SIZE) + { + status = NDIS_STATUS_INVALID_LENGTH; + break; + } + + if (OidRequest->DATA.SET_INFORMATION.InformationBufferLength > (TAP_MAX_MCAST_LIST * MACADDR_SIZE)) + { + status = NDIS_STATUS_MULTICAST_FULL; + OidRequest->DATA.SET_INFORMATION.BytesNeeded = TAP_MAX_MCAST_LIST * MACADDR_SIZE; + break; + } + + // BUGBUG!!! Is lock needed??? If so, use NDIS_RW_LOCK. Also apply to packet filter. + + NdisZeroMemory(Adapter->MCList, + TAP_MAX_MCAST_LIST * MACADDR_SIZE); + + NdisMoveMemory(Adapter->MCList, + OidRequest->DATA.SET_INFORMATION.InformationBuffer, + OidRequest->DATA.SET_INFORMATION.InformationBufferLength); + + Adapter->ulMCListSize = OidRequest->DATA.SET_INFORMATION.InformationBufferLength / MACADDR_SIZE; + + } while(FALSE); + return status; +} + +NDIS_STATUS +tapSetPacketFilter( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in ULONG PacketFilter + ) +{ + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + + // any bits not supported? + if (PacketFilter & ~(TAP_SUPPORTED_FILTERS)) + { + DEBUGP (("[TAP] Unsupported packet filter: 0x%08x\n", PacketFilter)); + status = NDIS_STATUS_NOT_SUPPORTED; + } + else + { + // Any actual filtering changes? + if (PacketFilter != Adapter->PacketFilter) + { + // + // Change the filtering modes on hardware + // + + // Save the new packet filter value + Adapter->PacketFilter = PacketFilter; + } + } + + return status; +} + +NDIS_STATUS +AdapterSetPowerD0( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +/*++ +Routine Description: + + NIC power has been restored to the working power state (D0). + Prepare the NIC for normal operation: + - Restore hardware context (packet filters, multicast addresses, MAC address, etc.) + - Enable interrupts and the NIC's DMA engine. + +Arguments: + + Adapter - Pointer to adapter block + +Return Value: + + NDIS_STATUS + +--*/ +{ + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + + DEBUGP (("[TAP] PowerState: Fully powered\n")); + + // Start data path... + + return status; +} + +NDIS_STATUS +AdapterSetPowerLow( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in NDIS_DEVICE_POWER_STATE PowerState + ) +/*++ +Routine Description: + + The NIC is about to be transitioned to a low power state. + Prepare the NIC for the sleeping state: + - Disable interrupts and the NIC's DMA engine, cancel timers. + - Save any hardware context that the NIC cannot preserve in + a sleeping state (packet filters, multicast addresses, + the current MAC address, etc.) + A miniport driver cannot access the NIC hardware after + the NIC has been set to the D3 state by the bus driver. + + Miniport drivers NDIS v6.30 and above + Do NOT wait for NDIS to return the ownership of all + NBLs from outstanding receive indications + Retain ownership of all the receive descriptors and + packet buffers previously owned by the hardware. + +Arguments: + + Adapter - Pointer to adapter block + PowerState - New power state + +Return Value: + + NDIS_STATUS + +--*/ +{ + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + + DEBUGP (("[TAP] PowerState: Low-power\n")); + + // + // Miniport drivers NDIS v6.20 and below are + // paused prior the low power transition + // + + // Check for paused state... + // Verify data path stopped... + + return status; +} + +NDIS_STATUS +tapSetInformation( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in PNDIS_OID_REQUEST OidRequest + ) +/*++ + +Routine Description: + + Helper function to perform a set OID request + +Arguments: + + Adapter - + NdisSetRequest - The OID to set + +Return Value: + + NDIS_STATUS + +--*/ +{ + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + + DBG_PRINT_OID_NAME(OidRequest->DATA.SET_INFORMATION.Oid); + + switch(OidRequest->DATA.SET_INFORMATION.Oid) + { + case OID_802_3_MULTICAST_LIST: + // + // Set the multicast address list on the NIC for packet reception. + // The NIC driver can set a limit on the number of multicast + // addresses bound protocol drivers can enable simultaneously. + // NDIS returns NDIS_STATUS_MULTICAST_FULL if a protocol driver + // exceeds this limit or if it specifies an invalid multicast + // address. + // + status = tapSetMulticastList(Adapter,OidRequest); + break; + + case OID_GEN_CURRENT_LOOKAHEAD: + // + // A protocol driver can set a suggested value for the number + // of bytes to be used in its binding; however, the underlying + // NIC driver is never required to limit its indications to + // the value set. + // + if (OidRequest->DATA.SET_INFORMATION.InformationBufferLength != sizeof(ULONG)) + { + OidRequest->DATA.SET_INFORMATION.BytesNeeded = sizeof(ULONG); + status = NDIS_STATUS_INVALID_LENGTH; + break; + } + + Adapter->ulLookahead = *(PULONG)OidRequest->DATA.SET_INFORMATION.InformationBuffer; + + OidRequest->DATA.SET_INFORMATION.BytesRead = sizeof(ULONG); + status = NDIS_STATUS_SUCCESS; + break; + + case OID_GEN_CURRENT_PACKET_FILTER: + // + // Program the hardware to indicate the packets + // of certain filter types. + // + if(OidRequest->DATA.SET_INFORMATION.InformationBufferLength != sizeof(ULONG)) + { + OidRequest->DATA.SET_INFORMATION.BytesNeeded = sizeof(ULONG); + status = NDIS_STATUS_INVALID_LENGTH; + break; + } + + OidRequest->DATA.SET_INFORMATION.BytesRead + = OidRequest->DATA.SET_INFORMATION.InformationBufferLength; + + status = tapSetPacketFilter( + Adapter, + *((PULONG)OidRequest->DATA.SET_INFORMATION.InformationBuffer) + ); + + break; + + case OID_PNP_SET_POWER: + { + // Sanity check. + if (OidRequest->DATA.SET_INFORMATION.InformationBufferLength + < sizeof(NDIS_DEVICE_POWER_STATE) + ) + { + status = NDIS_STATUS_INVALID_LENGTH; + } + else + { + NDIS_DEVICE_POWER_STATE PowerState; + + PowerState = *(PNDIS_DEVICE_POWER_STATE UNALIGNED)OidRequest->DATA.SET_INFORMATION.InformationBuffer; + OidRequest->DATA.SET_INFORMATION.BytesRead = sizeof(NDIS_DEVICE_POWER_STATE); + + if(PowerState < NdisDeviceStateD0 || + PowerState > NdisDeviceStateD3) + { + status = NDIS_STATUS_INVALID_DATA; + } + else + { + Adapter->CurrentPowerState = PowerState; + + if (PowerState == NdisDeviceStateD0) + { + status = AdapterSetPowerD0(Adapter); + } + else + { + status = AdapterSetPowerLow(Adapter, PowerState); + } + } + } + } + break; + +#if (NDIS_SUPPORT_NDIS61) + case OID_PNP_ADD_WAKE_UP_PATTERN: + case OID_PNP_REMOVE_WAKE_UP_PATTERN: + case OID_PNP_ENABLE_WAKE_UP: +#endif + ASSERT(!"NIC does not support wake on LAN OIDs"); + default: + // + // The entry point may by used by other requests + // + status = NDIS_STATUS_NOT_SUPPORTED; + break; + } + + return status; +} + +NDIS_STATUS +tapQueryInformation( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in PNDIS_OID_REQUEST OidRequest + ) +/*++ + +Routine Description: + + Helper function to perform a query OID request + +Arguments: + + Adapter - + OidRequest - The OID request that is being queried + +Return Value: + + NDIS_STATUS + +--*/ +{ + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + NDIS_MEDIUM Medium = TAP_MEDIUM_TYPE; + NDIS_HARDWARE_STATUS HardwareStatus = NdisHardwareStatusReady; + UCHAR VendorDesc[] = TAP_VENDOR_DESC; + ULONG ulInfo; + USHORT usInfo; + ULONG64 ulInfo64; + + // Default to returning the ULONG value + PVOID pInfo=NULL; + ULONG ulInfoLen = sizeof(ulInfo); + + // ATTENTION!!! Ignore OIDs to noisy to print... + if((OidRequest->DATA.QUERY_INFORMATION.Oid != OID_GEN_STATISTICS) + && (OidRequest->DATA.QUERY_INFORMATION.Oid != OID_IP4_OFFLOAD_STATS) + && (OidRequest->DATA.QUERY_INFORMATION.Oid != OID_IP6_OFFLOAD_STATS) + ) + { + DBG_PRINT_OID_NAME(OidRequest->DATA.QUERY_INFORMATION.Oid); + } + + // Dispatch based on object identifier (OID). + switch(OidRequest->DATA.QUERY_INFORMATION.Oid) + { + case OID_GEN_HARDWARE_STATUS: + // + // Specify the current hardware status of the underlying NIC as + // one of the following NDIS_HARDWARE_STATUS-type values. + // + pInfo = (PVOID) &HardwareStatus; + ulInfoLen = sizeof(NDIS_HARDWARE_STATUS); + break; + + case OID_802_3_PERMANENT_ADDRESS: + // + // Return the MAC address of the NIC burnt in the hardware. + // + pInfo = Adapter->PermanentAddress; + ulInfoLen = MACADDR_SIZE; + break; + + case OID_802_3_CURRENT_ADDRESS: + // + // Return the MAC address the NIC is currently programmed to + // use. Note that this address could be different from the + // permananent address as the user can override using + // registry. Read NdisReadNetworkAddress doc for more info. + // + pInfo = Adapter->CurrentAddress; + ulInfoLen = MACADDR_SIZE; + break; + + case OID_GEN_MEDIA_SUPPORTED: + // + // Return an array of media that are supported by the miniport. + // This miniport only supports one medium (Ethernet), so the OID + // returns identical results to OID_GEN_MEDIA_IN_USE. + // + + __fallthrough; + + case OID_GEN_MEDIA_IN_USE: + // + // Return an array of media that are currently in use by the + // miniport. This array should be a subset of the array returned + // by OID_GEN_MEDIA_SUPPORTED. + // + pInfo = &Medium; + ulInfoLen = sizeof(Medium); + break; + + case OID_GEN_MAXIMUM_TOTAL_SIZE: + // + // Specify the maximum total packet length, in bytes, the NIC + // supports including the header. A protocol driver might use + // this returned length as a gauge to determine the maximum + // size packet that a NIC driver could forward to the + // protocol driver. The miniport driver must never indicate + // up to the bound protocol driver packets received over the + // network that are longer than the packet size specified by + // OID_GEN_MAXIMUM_TOTAL_SIZE. + // + + __fallthrough; + + case OID_GEN_TRANSMIT_BLOCK_SIZE: + // + // The OID_GEN_TRANSMIT_BLOCK_SIZE OID specifies the minimum + // number of bytes that a single net packet occupies in the + // transmit buffer space of the NIC. In our case, the transmit + // block size is identical to its maximum packet size. + __fallthrough; + + case OID_GEN_RECEIVE_BLOCK_SIZE: + // + // The OID_GEN_RECEIVE_BLOCK_SIZE OID specifies the amount of + // storage, in bytes, that a single packet occupies in the receive + // buffer space of the NIC. + // + ulInfo = (ULONG) TAP_MAX_FRAME_SIZE; + pInfo = &ulInfo; + break; + + case OID_GEN_INTERRUPT_MODERATION: + { + PNDIS_INTERRUPT_MODERATION_PARAMETERS moderationParams + = (PNDIS_INTERRUPT_MODERATION_PARAMETERS)OidRequest->DATA.QUERY_INFORMATION.InformationBuffer; + + moderationParams->Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + moderationParams->Header.Revision = NDIS_INTERRUPT_MODERATION_PARAMETERS_REVISION_1; + moderationParams->Header.Size = NDIS_SIZEOF_INTERRUPT_MODERATION_PARAMETERS_REVISION_1; + moderationParams->Flags = 0; + moderationParams->InterruptModeration = NdisInterruptModerationNotSupported; + ulInfoLen = NDIS_SIZEOF_INTERRUPT_MODERATION_PARAMETERS_REVISION_1; + } + break; + + case OID_PNP_QUERY_POWER: + // Simply succeed this. + break; + + case OID_GEN_VENDOR_ID: + // + // Specify a three-byte IEEE-registered vendor code, followed + // by a single byte that the vendor assigns to identify a + // particular NIC. The IEEE code uniquely identifies the vendor + // and is the same as the three bytes appearing at the beginning + // of the NIC hardware address. Vendors without an IEEE-registered + // code should use the value 0xFFFFFF. + // + + ulInfo = TAP_VENDOR_ID; + pInfo = &ulInfo; + break; + + case OID_GEN_VENDOR_DESCRIPTION: + // + // Specify a zero-terminated string describing the NIC vendor. + // + pInfo = VendorDesc; + ulInfoLen = sizeof(VendorDesc); + break; + + case OID_GEN_VENDOR_DRIVER_VERSION: + // + // Specify the vendor-assigned version number of the NIC driver. + // The low-order half of the return value specifies the minor + // version; the high-order half specifies the major version. + // + + ulInfo = TAP_DRIVER_VENDOR_VERSION; + pInfo = &ulInfo; + break; + + case OID_GEN_DRIVER_VERSION: + // + // Specify the NDIS version in use by the NIC driver. The high + // byte is the major version number; the low byte is the minor + // version number. + // + usInfo = (USHORT) (TAP_NDIS_MAJOR_VERSION<<8) + TAP_NDIS_MINOR_VERSION; + pInfo = (PVOID) &usInfo; + ulInfoLen = sizeof(USHORT); + break; + + case OID_802_3_MAXIMUM_LIST_SIZE: + // + // The maximum number of multicast addresses the NIC driver + // can manage. This list is global for all protocols bound + // to (or above) the NIC. Consequently, a protocol can receive + // NDIS_STATUS_MULTICAST_FULL from the NIC driver when + // attempting to set the multicast address list, even if + // the number of elements in the given list is less than + // the number originally returned for this query. + // + + ulInfo = TAP_MAX_MCAST_LIST; + pInfo = &ulInfo; + break; + + case OID_GEN_XMIT_ERROR: + ulInfo = (ULONG) + (Adapter->TxAbortExcessCollisions + + Adapter->TxDmaUnderrun + + Adapter->TxLostCRS + + Adapter->TxLateCollisions+ + Adapter->TransmitFailuresOther); + pInfo = &ulInfo; + break; + + case OID_GEN_RCV_ERROR: + ulInfo = (ULONG) + (Adapter->RxCrcErrors + + Adapter->RxAlignmentErrors + + Adapter->RxDmaOverrunErrors + + Adapter->RxRuntErrors); + pInfo = &ulInfo; + break; + + case OID_GEN_RCV_DISCARDS: + ulInfo = (ULONG)Adapter->RxResourceErrors; + pInfo = &ulInfo; + break; + + case OID_GEN_RCV_NO_BUFFER: + ulInfo = (ULONG)Adapter->RxResourceErrors; + pInfo = &ulInfo; + break; + + case OID_GEN_XMIT_OK: + ulInfo64 = Adapter->FramesTxBroadcast + + Adapter->FramesTxMulticast + + Adapter->FramesTxDirected; + pInfo = &ulInfo64; + if (OidRequest->DATA.QUERY_INFORMATION.InformationBufferLength >= sizeof(ULONG64) || + OidRequest->DATA.QUERY_INFORMATION.InformationBufferLength == 0) + { + ulInfoLen = sizeof(ULONG64); + } + else + { + ulInfoLen = sizeof(ULONG); + } + + // We should always report that only 8 bytes are required to keep ndistest happy + OidRequest->DATA.QUERY_INFORMATION.BytesNeeded = sizeof(ULONG64); + break; + + case OID_GEN_RCV_OK: + ulInfo64 = Adapter->FramesRxBroadcast + + Adapter->FramesRxMulticast + + Adapter->FramesRxDirected; + + pInfo = &ulInfo64; + + if (OidRequest->DATA.QUERY_INFORMATION.InformationBufferLength >= sizeof(ULONG64) || + OidRequest->DATA.QUERY_INFORMATION.InformationBufferLength == 0) + { + ulInfoLen = sizeof(ULONG64); + } + else + { + ulInfoLen = sizeof(ULONG); + } + + // We should always report that only 8 bytes are required to keep ndistest happy + OidRequest->DATA.QUERY_INFORMATION.BytesNeeded = sizeof(ULONG64); + break; + + case OID_802_3_RCV_ERROR_ALIGNMENT: + + ulInfo = Adapter->RxAlignmentErrors; + pInfo = &ulInfo; + break; + + case OID_802_3_XMIT_ONE_COLLISION: + + ulInfo = Adapter->OneRetry; + pInfo = &ulInfo; + break; + + case OID_802_3_XMIT_MORE_COLLISIONS: + + ulInfo = Adapter->MoreThanOneRetry; + pInfo = &ulInfo; + break; + + case OID_802_3_XMIT_DEFERRED: + + ulInfo = Adapter->TxOKButDeferred; + pInfo = &ulInfo; + break; + + case OID_802_3_XMIT_MAX_COLLISIONS: + + ulInfo = Adapter->TxAbortExcessCollisions; + pInfo = &ulInfo; + break; + + case OID_802_3_RCV_OVERRUN: + + ulInfo = Adapter->RxDmaOverrunErrors; + pInfo = &ulInfo; + break; + + case OID_802_3_XMIT_UNDERRUN: + + ulInfo = Adapter->TxDmaUnderrun; + pInfo = &ulInfo; + break; + + case OID_GEN_STATISTICS: + + if (OidRequest->DATA.QUERY_INFORMATION.InformationBufferLength < sizeof(NDIS_STATISTICS_INFO)) + { + status = NDIS_STATUS_INVALID_LENGTH; + OidRequest->DATA.QUERY_INFORMATION.BytesNeeded = sizeof(NDIS_STATISTICS_INFO); + break; + } + else + { + PNDIS_STATISTICS_INFO Statistics + = (PNDIS_STATISTICS_INFO)OidRequest->DATA.QUERY_INFORMATION.InformationBuffer; + + {C_ASSERT(sizeof(NDIS_STATISTICS_INFO) >= NDIS_SIZEOF_STATISTICS_INFO_REVISION_1);} + Statistics->Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + Statistics->Header.Size = NDIS_SIZEOF_STATISTICS_INFO_REVISION_1; + Statistics->Header.Revision = NDIS_STATISTICS_INFO_REVISION_1; + + Statistics->SupportedStatistics = TAP_SUPPORTED_STATISTICS; + + /* Bytes in */ + Statistics->ifHCInOctets = + Adapter->BytesRxDirected + + Adapter->BytesRxMulticast + + Adapter->BytesRxBroadcast; + + Statistics->ifHCInUcastOctets = + Adapter->BytesRxDirected; + + Statistics->ifHCInMulticastOctets = + Adapter->BytesRxMulticast; + + Statistics->ifHCInBroadcastOctets = + Adapter->BytesRxBroadcast; + + /* Packets in */ + Statistics->ifHCInUcastPkts = + Adapter->FramesRxDirected; + + Statistics->ifHCInMulticastPkts = + Adapter->FramesRxMulticast; + + Statistics->ifHCInBroadcastPkts = + Adapter->FramesRxBroadcast; + + /* Errors in */ + Statistics->ifInErrors = + Adapter->RxCrcErrors + + Adapter->RxAlignmentErrors + + Adapter->RxDmaOverrunErrors + + Adapter->RxRuntErrors; + + Statistics->ifInDiscards = + Adapter->RxResourceErrors; + + + /* Bytes out */ + Statistics->ifHCOutOctets = + Adapter->BytesTxDirected + + Adapter->BytesTxMulticast + + Adapter->BytesTxBroadcast; + + Statistics->ifHCOutUcastOctets = + Adapter->BytesTxDirected; + + Statistics->ifHCOutMulticastOctets = + Adapter->BytesTxMulticast; + + Statistics->ifHCOutBroadcastOctets = + Adapter->BytesTxBroadcast; + + /* Packets out */ + Statistics->ifHCOutUcastPkts = + Adapter->FramesTxDirected; + + Statistics->ifHCOutMulticastPkts = + Adapter->FramesTxMulticast; + + Statistics->ifHCOutBroadcastPkts = + Adapter->FramesTxBroadcast; + + /* Errors out */ + Statistics->ifOutErrors = + Adapter->TxAbortExcessCollisions + + Adapter->TxDmaUnderrun + + Adapter->TxLostCRS + + Adapter->TxLateCollisions+ + Adapter->TransmitFailuresOther; + + Statistics->ifOutDiscards = 0ULL; + + ulInfoLen = NDIS_SIZEOF_STATISTICS_INFO_REVISION_1; + } + + break; + + // TODO: Inplement these query information requests. + case OID_GEN_RECEIVE_BUFFER_SPACE: + case OID_GEN_MAXIMUM_SEND_PACKETS: + case OID_GEN_TRANSMIT_QUEUE_LENGTH: + case OID_802_3_XMIT_HEARTBEAT_FAILURE: + case OID_802_3_XMIT_TIMES_CRS_LOST: + case OID_802_3_XMIT_LATE_COLLISIONS: + + default: + // + // The entry point may by used by other requests + // + status = NDIS_STATUS_NOT_SUPPORTED; + break; + } + + if (status == NDIS_STATUS_SUCCESS) + { + ASSERT(ulInfoLen > 0); + + if (ulInfoLen <= OidRequest->DATA.QUERY_INFORMATION.InformationBufferLength) + { + if(pInfo) + { + // Copy result into InformationBuffer + NdisMoveMemory( + OidRequest->DATA.QUERY_INFORMATION.InformationBuffer, + pInfo, + ulInfoLen + ); + } + + OidRequest->DATA.QUERY_INFORMATION.BytesWritten = ulInfoLen; + } + else + { + // too short + OidRequest->DATA.QUERY_INFORMATION.BytesNeeded = ulInfoLen; + status = NDIS_STATUS_BUFFER_TOO_SHORT; + } + } + + return status; +} + +NDIS_STATUS +AdapterOidRequest( + __in NDIS_HANDLE MiniportAdapterContext, + __in PNDIS_OID_REQUEST OidRequest + ) +/*++ + +Routine Description: + + Entry point called by NDIS to get or set the value of a specified OID. + +Arguments: + + MiniportAdapterContext - Our adapter handle + NdisRequest - The OID request to handle + +Return Value: + + Return code from the NdisRequest below. + +--*/ +{ + PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; + NDIS_STATUS status; + + // Dispatch based on request type. + switch (OidRequest->RequestType) + { + case NdisRequestSetInformation: + status = tapSetInformation(adapter,OidRequest); + break; + + case NdisRequestQueryInformation: + case NdisRequestQueryStatistics: + status = tapQueryInformation(adapter,OidRequest); + break; + + case NdisRequestMethod: // TAP doesn't need to respond to this request type. + default: + // + // The entry point may by used by other requests + // + status = NDIS_STATUS_NOT_SUPPORTED; + break; + } + + return status; +} + +VOID +AdapterCancelOidRequest( + __in NDIS_HANDLE MiniportAdapterContext, + __in PVOID RequestId + ) +{ + PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; + + UNREFERENCED_PARAMETER(RequestId); + + // + // This miniport sample does not pend any OID requests, so we don't have + // to worry about cancelling them. + // +} + diff --git a/windows/TapDriver6/proto.h b/windows/TapDriver6/proto.h new file mode 100644 index 0000000..cc23de6 --- /dev/null +++ b/windows/TapDriver6/proto.h @@ -0,0 +1,224 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +//============================================================ +// MAC address, Ethernet header, and ARP +//============================================================ + +#pragma pack(1) + +#define IP_HEADER_SIZE 20 +#define IPV6_HEADER_SIZE 40 + +#define MACADDR_SIZE 6 +typedef unsigned char MACADDR[MACADDR_SIZE]; + +typedef unsigned long IPADDR; +typedef unsigned char IPV6ADDR[16]; + +//----------------- +// Ethernet address +//----------------- + +typedef struct { + MACADDR addr; +} ETH_ADDR; + +typedef struct { + ETH_ADDR list[TAP_MAX_MCAST_LIST]; +} MC_LIST; + + +// BUGBUG!!! Consider using ststem defines in netiodef.h!!! + +//---------------- +// Ethernet header +//---------------- +typedef struct +{ + MACADDR dest; /* destination eth addr */ + MACADDR src; /* source ether addr */ + USHORT proto; /* packet type ID field */ +} ETH_HEADER, *PETH_HEADER; + +//---------------- +// ARP packet +//---------------- + +typedef struct + { + MACADDR m_MAC_Destination; // Reverse these two + MACADDR m_MAC_Source; // to answer ARP requests + USHORT m_Proto; // 0x0806 + +# define MAC_ADDR_TYPE 0x0001 + USHORT m_MAC_AddressType; // 0x0001 + + USHORT m_PROTO_AddressType; // 0x0800 + UCHAR m_MAC_AddressSize; // 0x06 + UCHAR m_PROTO_AddressSize; // 0x04 + +# define ARP_REQUEST 0x0001 +# define ARP_REPLY 0x0002 + USHORT m_ARP_Operation; // 0x0001 for ARP request, 0x0002 for ARP reply + + MACADDR m_ARP_MAC_Source; + IPADDR m_ARP_IP_Source; + MACADDR m_ARP_MAC_Destination; + IPADDR m_ARP_IP_Destination; + } +ARP_PACKET, *PARP_PACKET; + +//---------- +// IP Header +//---------- + +typedef struct { +# define IPH_GET_VER(v) (((v) >> 4) & 0x0F) +# define IPH_GET_LEN(v) (((v) & 0x0F) << 2) + UCHAR version_len; + + UCHAR tos; + USHORT tot_len; + USHORT id; + +# define IP_OFFMASK 0x1fff + USHORT frag_off; + + UCHAR ttl; + +# define IPPROTO_UDP 17 /* UDP protocol */ +# define IPPROTO_TCP 6 /* TCP protocol */ +# define IPPROTO_ICMP 1 /* ICMP protocol */ +# define IPPROTO_IGMP 2 /* IGMP protocol */ + UCHAR protocol; + + USHORT check; + ULONG saddr; + ULONG daddr; + /* The options start here. */ +} IPHDR; + +//----------- +// UDP header +//----------- + +typedef struct { + USHORT source; + USHORT dest; + USHORT len; + USHORT check; +} UDPHDR; + +//-------------------------- +// TCP header, per RFC 793. +//-------------------------- + +typedef struct { + USHORT source; /* source port */ + USHORT dest; /* destination port */ + ULONG seq; /* sequence number */ + ULONG ack_seq; /* acknowledgement number */ + +# define TCPH_GET_DOFF(d) (((d) & 0xF0) >> 2) + UCHAR doff_res; + +# define TCPH_FIN_MASK (1<<0) +# define TCPH_SYN_MASK (1<<1) +# define TCPH_RST_MASK (1<<2) +# define TCPH_PSH_MASK (1<<3) +# define TCPH_ACK_MASK (1<<4) +# define TCPH_URG_MASK (1<<5) +# define TCPH_ECE_MASK (1<<6) +# define TCPH_CWR_MASK (1<<7) + UCHAR flags; + + USHORT window; + USHORT check; + USHORT urg_ptr; +} TCPHDR; + +#define TCPOPT_EOL 0 +#define TCPOPT_NOP 1 +#define TCPOPT_MAXSEG 2 +#define TCPOLEN_MAXSEG 4 + +//------------ +// IPv6 Header +//------------ + +typedef struct { + UCHAR version_prio; + UCHAR flow_lbl[3]; + USHORT payload_len; +# define IPPROTO_ICMPV6 0x3a /* ICMP protocol v6 */ + UCHAR nexthdr; + UCHAR hop_limit; + IPV6ADDR saddr; + IPV6ADDR daddr; +} IPV6HDR; + +//-------------------------------------------- +// IPCMPv6 NS/NA Packets (RFC4443 and RFC4861) +//-------------------------------------------- + +// Neighbor Solictiation - RFC 4861, 4.3 +// (this is just the ICMPv6 part of the packet) +typedef struct { + UCHAR type; +# define ICMPV6_TYPE_NS 135 // neighbour solicitation + UCHAR code; +# define ICMPV6_CODE_0 0 // no specific sub-code for NS/NA + USHORT checksum; + ULONG reserved; + IPV6ADDR target_addr; +} ICMPV6_NS; + +// Neighbor Advertisement - RFC 4861, 4.4 + 4.6/4.6.1 +// (this is just the ICMPv6 payload) +typedef struct { + UCHAR type; +# define ICMPV6_TYPE_NA 136 // neighbour advertisement + UCHAR code; +# define ICMPV6_CODE_0 0 // no specific sub-code for NS/NA + USHORT checksum; + UCHAR rso_bits; // Router(0), Solicited(2), Ovrrd(4) + UCHAR reserved[3]; + IPV6ADDR target_addr; +// always include "Target Link-layer Address" option (RFC 4861 4.6.1) + UCHAR opt_type; +#define ICMPV6_OPTION_TLLA 2 + UCHAR opt_length; +#define ICMPV6_LENGTH_TLLA 1 // multiplied by 8 -> 1 = 8 bytes + MACADDR target_macaddr; +} ICMPV6_NA; + +// this is the complete packet with Ethernet and IPv6 headers +typedef struct { + ETH_HEADER eth; + IPV6HDR ipv6; + ICMPV6_NA icmpv6; +} ICMPV6_NA_PKT; + +#pragma pack() diff --git a/windows/TapDriver6/prototypes.h b/windows/TapDriver6/prototypes.h new file mode 100644 index 0000000..a48d35e --- /dev/null +++ b/windows/TapDriver6/prototypes.h @@ -0,0 +1,91 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef TAP_PROTOTYPES_DEFINED +#define TAP_PROTOTYPES_DEFINED + +DRIVER_INITIALIZE DriverEntry; + +//VOID AdapterFreeResources +// ( +// TapAdapterPointer p_Adapter +// ); +// + +// +//NTSTATUS TapDeviceHook +// ( +// IN PDEVICE_OBJECT p_DeviceObject, +// IN PIRP p_IRP +// ); +// + +NDIS_STATUS +CreateTapDevice( + __in PTAP_ADAPTER_CONTEXT Adapter + ); + +VOID +DestroyTapDevice( + __in PTAP_ADAPTER_CONTEXT Adapter + ); + +// Flush the pending send TAP packet queue. +VOID +tapFlushSendPacketQueue( + __in PTAP_ADAPTER_CONTEXT Adapter + ); + +VOID +IndicateReceivePacket( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in PUCHAR packetData, + __in const unsigned int packetLength + ); + +/* +BOOLEAN +ProcessDHCP( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in const ETH_HEADER *eth, + __in const IPHDR *ip, + __in const UDPHDR *udp, + __in const DHCP *dhcp, + __in int optlen + ); +*/ + +/* +BOOLEAN +ProcessARP( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in const PARP_PACKET src, + __in const IPADDR adapter_ip, + __in const IPADDR ip_network, + __in const IPADDR ip_netmask, + __in const MACADDR mac + ); +*/ + +#endif diff --git a/windows/TapDriver6/resource.h b/windows/TapDriver6/resource.h new file mode 100644 index 0000000..e736408 --- /dev/null +++ b/windows/TapDriver6/resource.h @@ -0,0 +1,1573 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by resource.rc +// +#define SW_HIDE 0 +#define HIDE_WINDOW 0 +#define WM_NULL 0x0000 +#define WA_INACTIVE 0 +#define HTNOWHERE 0 +#define SMTO_NORMAL 0x0000 +#define ICON_SMALL 0 +#define SIZE_RESTORED 0 +#define BN_CLICKED 0 +#define BST_UNCHECKED 0x0000 +#define HDS_HORZ 0x0000 +#define TBSTYLE_BUTTON 0x0000 +#define TBS_HORZ 0x0000 +#define TBS_BOTTOM 0x0000 +#define TBS_RIGHT 0x0000 +#define LVS_ICON 0x0000 +#define LVS_ALIGNTOP 0x0000 +#define TCS_TABS 0x0000 +#define TCS_SINGLELINE 0x0000 +#define TCS_RIGHTJUSTIFY 0x0000 +#define DTS_SHORTDATEFORMAT 0x0000 +#define PGS_VERT 0x00000000 +#define LANG_NEUTRAL 0x00 +#define SUBLANG_NEUTRAL 0x00 +#define SORT_DEFAULT 0x0 +#define SORT_JAPANESE_XJIS 0x0 +#define SORT_CHINESE_BIG5 0x0 +#define SORT_CHINESE_PRCP 0x0 +#define SORT_KOREAN_KSC 0x0 +#define SORT_HUNGARIAN_DEFAULT 0x0 +#define SORT_GEORGIAN_TRADITIONAL 0x0 +#define _USE_DECLSPECS_FOR_SAL 0 +#define _USE_ATTRIBUTES_FOR_SAL 0 +#define __drv_typeConst 0 +#define VER_DEBUG 0 +#define VER_PRERELEASE 0 +#define PRODUCT_TAP_WIN_MINOR 0 +#define WINAPI_PARTITION_DESKTOP 0x00000001 +#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 +#define MINIMUM_RESERVED_MANIFEST_RESOURCE_ID 1 +#define SW_SHOWNORMAL 1 +#define SW_NORMAL 1 +#define SHOW_OPENWINDOW 1 +#define SW_PARENTCLOSING 1 +#define VK_LBUTTON 0x01 +#define WM_CREATE 0x0001 +#define WA_ACTIVE 1 +#define PWR_OK 1 +#define PWR_SUSPENDREQUEST 1 +#define NFR_ANSI 1 +#define UIS_SET 1 +#define UISF_HIDEFOCUS 0x1 +#define XBUTTON1 0x0001 +#define WMSZ_LEFT 1 +#define HTCLIENT 1 +#define SMTO_BLOCK 0x0001 +#define MA_ACTIVATE 1 +#define ICON_BIG 1 +#define SIZE_MINIMIZED 1 +#define MK_LBUTTON 0x0001 +#define TME_HOVER 0x00000001 +#define CS_VREDRAW 0x0001 +#define CF_TEXT 1 +#define SCF_ISSECURE 0x00000001 +#define IDOK 1 +#define BN_PAINT 1 +#define BST_CHECKED 0x0001 +#define TBSTYLE_SEP 0x0001 +#define TTS_ALWAYSTIP 0x01 +#define TBS_AUTOTICKS 0x0001 +#define UDS_WRAP 0x0001 +#define PBS_SMOOTH 0x01 +#define LWS_TRANSPARENT 0x0001 +#define LVS_REPORT 0x0001 +#define TVS_HASBUTTONS 0x0001 +#define TVS_EX_NOSINGLECOLLAPSE 0x0001 +#define TCS_SCROLLOPPOSITE 0x0001 +#define ACS_CENTER 0x0001 +#define MCS_DAYSTATE 0x0001 +#define DTS_UPDOWN 0x0001 +#define PGS_HORZ 0x00000001 +#define NFS_EDIT 0x0001 +#define BCSIF_GLYPH 0x0001 +#define BCSS_NOSPLIT 0x0001 +#define LANG_ARABIC 0x01 +#define SUBLANG_DEFAULT 0x01 +#define SUBLANG_AFRIKAANS_SOUTH_AFRICA 0x01 +#define SUBLANG_ALBANIAN_ALBANIA 0x01 +#define SUBLANG_ALSATIAN_FRANCE 0x01 +#define SUBLANG_AMHARIC_ETHIOPIA 0x01 +#define SUBLANG_ARABIC_SAUDI_ARABIA 0x01 +#define SUBLANG_ARMENIAN_ARMENIA 0x01 +#define SUBLANG_ASSAMESE_INDIA 0x01 +#define SUBLANG_AZERI_LATIN 0x01 +#define SUBLANG_AZERBAIJANI_AZERBAIJAN_LATIN 0x01 +#define SUBLANG_BANGLA_INDIA 0x01 +#define SUBLANG_BASHKIR_RUSSIA 0x01 +#define SUBLANG_BASQUE_BASQUE 0x01 +#define SUBLANG_BELARUSIAN_BELARUS 0x01 +#define SUBLANG_BENGALI_INDIA 0x01 +#define SUBLANG_BRETON_FRANCE 0x01 +#define SUBLANG_BULGARIAN_BULGARIA 0x01 +#define SUBLANG_CATALAN_CATALAN 0x01 +#define SUBLANG_CENTRAL_KURDISH_IRAQ 0x01 +#define SUBLANG_CHEROKEE_CHEROKEE 0x01 +#define SUBLANG_CHINESE_TRADITIONAL 0x01 +#define SUBLANG_CORSICAN_FRANCE 0x01 +#define SUBLANG_CZECH_CZECH_REPUBLIC 0x01 +#define SUBLANG_CROATIAN_CROATIA 0x01 +#define SUBLANG_DANISH_DENMARK 0x01 +#define SUBLANG_DARI_AFGHANISTAN 0x01 +#define SUBLANG_DIVEHI_MALDIVES 0x01 +#define SUBLANG_DUTCH 0x01 +#define SUBLANG_ENGLISH_US 0x01 +#define SUBLANG_ESTONIAN_ESTONIA 0x01 +#define SUBLANG_FAEROESE_FAROE_ISLANDS 0x01 +#define SUBLANG_FILIPINO_PHILIPPINES 0x01 +#define SUBLANG_FINNISH_FINLAND 0x01 +#define SUBLANG_FRENCH 0x01 +#define SUBLANG_FRISIAN_NETHERLANDS 0x01 +#define SUBLANG_GALICIAN_GALICIAN 0x01 +#define SUBLANG_GEORGIAN_GEORGIA 0x01 +#define SUBLANG_GERMAN 0x01 +#define SUBLANG_GREEK_GREECE 0x01 +#define SUBLANG_GREENLANDIC_GREENLAND 0x01 +#define SUBLANG_GUJARATI_INDIA 0x01 +#define SUBLANG_HAUSA_NIGERIA_LATIN 0x01 +#define SUBLANG_HAWAIIAN_US 0x01 +#define SUBLANG_HEBREW_ISRAEL 0x01 +#define SUBLANG_HINDI_INDIA 0x01 +#define SUBLANG_HUNGARIAN_HUNGARY 0x01 +#define SUBLANG_ICELANDIC_ICELAND 0x01 +#define SUBLANG_IGBO_NIGERIA 0x01 +#define SUBLANG_INDONESIAN_INDONESIA 0x01 +#define SUBLANG_INUKTITUT_CANADA 0x01 +#define SUBLANG_ITALIAN 0x01 +#define SUBLANG_JAPANESE_JAPAN 0x01 +#define SUBLANG_KANNADA_INDIA 0x01 +#define SUBLANG_KAZAK_KAZAKHSTAN 0x01 +#define SUBLANG_KHMER_CAMBODIA 0x01 +#define SUBLANG_KICHE_GUATEMALA 0x01 +#define SUBLANG_KINYARWANDA_RWANDA 0x01 +#define SUBLANG_KONKANI_INDIA 0x01 +#define SUBLANG_KOREAN 0x01 +#define SUBLANG_KYRGYZ_KYRGYZSTAN 0x01 +#define SUBLANG_LAO_LAO 0x01 +#define SUBLANG_LATVIAN_LATVIA 0x01 +#define SUBLANG_LITHUANIAN 0x01 +#define SUBLANG_LUXEMBOURGISH_LUXEMBOURG 0x01 +#define SUBLANG_MACEDONIAN_MACEDONIA 0x01 +#define SUBLANG_MALAY_MALAYSIA 0x01 +#define SUBLANG_MALAYALAM_INDIA 0x01 +#define SUBLANG_MALTESE_MALTA 0x01 +#define SUBLANG_MAORI_NEW_ZEALAND 0x01 +#define SUBLANG_MAPUDUNGUN_CHILE 0x01 +#define SUBLANG_MARATHI_INDIA 0x01 +#define SUBLANG_MOHAWK_MOHAWK 0x01 +#define SUBLANG_MONGOLIAN_CYRILLIC_MONGOLIA 0x01 +#define SUBLANG_NEPALI_NEPAL 0x01 +#define SUBLANG_NORWEGIAN_BOKMAL 0x01 +#define SUBLANG_OCCITAN_FRANCE 0x01 +#define SUBLANG_ODIA_INDIA 0x01 +#define SUBLANG_ORIYA_INDIA 0x01 +#define SUBLANG_PASHTO_AFGHANISTAN 0x01 +#define SUBLANG_PERSIAN_IRAN 0x01 +#define SUBLANG_POLISH_POLAND 0x01 +#define SUBLANG_PORTUGUESE_BRAZILIAN 0x01 +#define SUBLANG_PUNJABI_INDIA 0x01 +#define SUBLANG_QUECHUA_BOLIVIA 0x01 +#define SUBLANG_ROMANIAN_ROMANIA 0x01 +#define SUBLANG_ROMANSH_SWITZERLAND 0x01 +#define SUBLANG_RUSSIAN_RUSSIA 0x01 +#define SUBLANG_SAKHA_RUSSIA 0x01 +#define SUBLANG_SAMI_NORTHERN_NORWAY 0x01 +#define SUBLANG_SANSKRIT_INDIA 0x01 +#define SUBLANG_SCOTTISH_GAELIC 0x01 +#define SUBLANG_SERBIAN_CROATIA 0x01 +#define SUBLANG_SINDHI_INDIA 0x01 +#define SUBLANG_SINHALESE_SRI_LANKA 0x01 +#define SUBLANG_SOTHO_NORTHERN_SOUTH_AFRICA 0x01 +#define SUBLANG_SLOVAK_SLOVAKIA 0x01 +#define SUBLANG_SLOVENIAN_SLOVENIA 0x01 +#define SUBLANG_SPANISH 0x01 +#define SUBLANG_SWAHILI_KENYA 0x01 +#define SUBLANG_SWEDISH 0x01 +#define SUBLANG_SYRIAC_SYRIA 0x01 +#define SUBLANG_TAJIK_TAJIKISTAN 0x01 +#define SUBLANG_TAMIL_INDIA 0x01 +#define SUBLANG_TATAR_RUSSIA 0x01 +#define SUBLANG_TELUGU_INDIA 0x01 +#define SUBLANG_THAI_THAILAND 0x01 +#define SUBLANG_TIBETAN_PRC 0x01 +#define SUBLANG_TIGRINYA_ETHIOPIA 0x01 +#define SUBLANG_TSWANA_SOUTH_AFRICA 0x01 +#define SUBLANG_TURKISH_TURKEY 0x01 +#define SUBLANG_TURKMEN_TURKMENISTAN 0x01 +#define SUBLANG_UIGHUR_PRC 0x01 +#define SUBLANG_UKRAINIAN_UKRAINE 0x01 +#define SUBLANG_UPPER_SORBIAN_GERMANY 0x01 +#define SUBLANG_URDU_PAKISTAN 0x01 +#define SUBLANG_UZBEK_LATIN 0x01 +#define SUBLANG_VIETNAMESE_VIETNAM 0x01 +#define SUBLANG_WELSH_UNITED_KINGDOM 0x01 +#define SUBLANG_WOLOF_SENEGAL 0x01 +#define SUBLANG_XHOSA_SOUTH_AFRICA 0x01 +#define SUBLANG_YAKUT_RUSSIA 0x01 +#define SUBLANG_YI_PRC 0x01 +#define SUBLANG_YORUBA_NIGERIA 0x01 +#define SUBLANG_ZULU_SOUTH_AFRICA 0x01 +#define SORT_INVARIANT_MATH 0x1 +#define SORT_JAPANESE_UNICODE 0x1 +#define SORT_CHINESE_UNICODE 0x1 +#define SORT_KOREAN_UNICODE 0x1 +#define SORT_GERMAN_PHONE_BOOK 0x1 +#define SORT_HUNGARIAN_TECHNICAL 0x1 +#define SORT_GEORGIAN_MODERN 0x1 +#define __drv_typeCond 1 +#define VS_VERSION_INFO 1 +#define VFFF_ISSHAREDFILE 0x0001 +#define VFF_CURNEDEST 0x0001 +#define VIFF_FORCEINSTALL 0x0001 +#define WINAPI_PARTITION_APP 0x00000002 +#define ISOLATIONAWARE_MANIFEST_RESOURCE_ID 2 +#define SW_SHOWMINIMIZED 2 +#define SHOW_ICONWINDOW 2 +#define SW_OTHERZOOM 2 +#define VK_RBUTTON 0x02 +#define WM_DESTROY 0x0002 +#define WA_CLICKACTIVE 2 +#define PWR_SUSPENDRESUME 2 +#define NFR_UNICODE 2 +#define UIS_CLEAR 2 +#define UISF_HIDEACCEL 0x2 +#define XBUTTON2 0x0002 +#define WMSZ_RIGHT 2 +#define HTCAPTION 2 +#define SMTO_ABORTIFHUNG 0x0002 +#define MA_ACTIVATEANDEAT 2 +#define ICON_SMALL2 2 +#define SIZE_MAXIMIZED 2 +#define MK_RBUTTON 0x0002 +#define TME_LEAVE 0x00000002 +#define CS_HREDRAW 0x0002 +#define CF_BITMAP 2 +#define IDCANCEL 2 +#define BN_HILITE 2 +#define BST_INDETERMINATE 0x0002 +#define HDS_BUTTONS 0x0002 +#define TBSTYLE_CHECK 0x0002 +#define TTS_NOPREFIX 0x02 +#define TBS_VERT 0x0002 +#define UDS_SETBUDDYINT 0x0002 +#define LWS_IGNORERETURN 0x0002 +#define LVS_SMALLICON 0x0002 +#define TVS_HASLINES 0x0002 +#define TVS_EX_MULTISELECT 0x0002 +#define TCS_BOTTOM 0x0002 +#define TCS_RIGHT 0x0002 +#define ACS_TRANSPARENT 0x0002 +#define MCS_MULTISELECT 0x0002 +#define DTS_SHOWNONE 0x0002 +#define PGS_AUTOSCROLL 0x00000002 +#define NFS_STATIC 0x0002 +#define BCSIF_IMAGE 0x0002 +#define BCSS_STRETCH 0x0002 +#define LANG_BULGARIAN 0x02 +#define SUBLANG_SYS_DEFAULT 0x02 +#define SUBLANG_ARABIC_IRAQ 0x02 +#define SUBLANG_AZERI_CYRILLIC 0x02 +#define SUBLANG_AZERBAIJANI_AZERBAIJAN_CYRILLIC 0x02 +#define SUBLANG_BANGLA_BANGLADESH 0x02 +#define SUBLANG_BENGALI_BANGLADESH 0x02 +#define SUBLANG_CHINESE_SIMPLIFIED 0x02 +#define SUBLANG_DUTCH_BELGIAN 0x02 +#define SUBLANG_ENGLISH_UK 0x02 +#define SUBLANG_FRENCH_BELGIAN 0x02 +#define SUBLANG_FULAH_SENEGAL 0x02 +#define SUBLANG_GERMAN_SWISS 0x02 +#define SUBLANG_INUKTITUT_CANADA_LATIN 0x02 +#define SUBLANG_IRISH_IRELAND 0x02 +#define SUBLANG_ITALIAN_SWISS 0x02 +#define SUBLANG_KASHMIRI_SASIA 0x02 +#define SUBLANG_KASHMIRI_INDIA 0x02 +#define SUBLANG_LOWER_SORBIAN_GERMANY 0x02 +#define SUBLANG_MALAY_BRUNEI_DARUSSALAM 0x02 +#define SUBLANG_MONGOLIAN_PRC 0x02 +#define SUBLANG_NEPALI_INDIA 0x02 +#define SUBLANG_NORWEGIAN_NYNORSK 0x02 +#define SUBLANG_PORTUGUESE 0x02 +#define SUBLANG_PULAR_SENEGAL 0x02 +#define SUBLANG_PUNJABI_PAKISTAN 0x02 +#define SUBLANG_QUECHUA_ECUADOR 0x02 +#define SUBLANG_SAMI_NORTHERN_SWEDEN 0x02 +#define SUBLANG_SERBIAN_LATIN 0x02 +#define SUBLANG_SINDHI_PAKISTAN 0x02 +#define SUBLANG_SINDHI_AFGHANISTAN 0x02 +#define SUBLANG_SPANISH_MEXICAN 0x02 +#define SUBLANG_SWEDISH_FINLAND 0x02 +#define SUBLANG_TAMAZIGHT_ALGERIA_LATIN 0x02 +#define SUBLANG_TAMIL_SRI_LANKA 0x02 +#define SUBLANG_TIGRIGNA_ERITREA 0x02 +#define SUBLANG_TIGRINYA_ERITREA 0x02 +#define SUBLANG_TSWANA_BOTSWANA 0x02 +#define SUBLANG_URDU_INDIA 0x02 +#define SUBLANG_UZBEK_CYRILLIC 0x02 +#define SUBLANG_VALENCIAN_VALENCIA 0x02 +#define SORT_CHINESE_PRC 0x2 +#define __drv_typeBitset 2 +#define VFF_FILEINUSE 0x0002 +#define VIFF_DONTDELETEOLD 0x0002 +#define VER_PRODUCTMINORVERSION 2 +#define ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID 3 +#define SW_SHOWMAXIMIZED 3 +#define SW_MAXIMIZE 3 +#define SHOW_FULLSCREEN 3 +#define SW_PARENTOPENING 3 +#define VK_CANCEL 0x03 +#define WM_MOVE 0x0003 +#define PWR_CRITICALRESUME 3 +#define NF_QUERY 3 +#define UIS_INITIALIZE 3 +#define WMSZ_TOP 3 +#define HTSYSMENU 3 +#define MA_NOACTIVATE 3 +#define SIZE_MAXSHOW 3 +#define CF_METAFILEPICT 3 +#define IDABORT 3 +#define BN_UNHILITE 3 +#define LVS_LIST 0x0003 +#define LVS_TYPEMASK 0x0003 +#define LANG_CATALAN 0x03 +#define LANG_VALENCIAN 0x03 +#define SUBLANG_CUSTOM_DEFAULT 0x03 +#define SUBLANG_ARABIC_EGYPT 0x03 +#define SUBLANG_CHINESE_HONGKONG 0x03 +#define SUBLANG_ENGLISH_AUS 0x03 +#define SUBLANG_FRENCH_CANADIAN 0x03 +#define SUBLANG_GERMAN_AUSTRIAN 0x03 +#define SUBLANG_QUECHUA_PERU 0x03 +#define SUBLANG_SAMI_NORTHERN_FINLAND 0x03 +#define SUBLANG_SERBIAN_CYRILLIC 0x03 +#define SUBLANG_SPANISH_MODERN 0x03 +#define SORT_CHINESE_BOPOMOFO 0x3 +#define __drv_typeExpr 3 +#define PRODUCT_TAP_WIN_MAJOR 3 +#define SW_SHOWNOACTIVATE 4 +#define SHOW_OPENNOACTIVATE 4 +#define SW_OTHERUNZOOM 4 +#define VK_MBUTTON 0x04 +#define NF_REQUERY 4 +#define UISF_ACTIVE 0x4 +#define WMSZ_TOPLEFT 4 +#define HTGROWBOX 4 +#define MA_NOACTIVATEANDEAT 4 +#define SIZE_MAXHIDE 4 +#define MK_SHIFT 0x0004 +#define CF_SYLK 4 +#define IDRETRY 4 +#define BN_DISABLE 4 +#define BST_PUSHED 0x0004 +#define HDS_HOTTRACK 0x0004 +#define TBSTYLE_GROUP 0x0004 +#define TBS_TOP 0x0004 +#define TBS_LEFT 0x0004 +#define UDS_ALIGNRIGHT 0x0004 +#define PBS_VERTICAL 0x04 +#define LWS_NOPREFIX 0x0004 +#define LVS_SINGLESEL 0x0004 +#define TVS_LINESATROOT 0x0004 +#define TVS_EX_DOUBLEBUFFER 0x0004 +#define TCS_MULTISELECT 0x0004 +#define ACS_AUTOPLAY 0x0004 +#define MCS_WEEKNUMBERS 0x0004 +#define DTS_LONGDATEFORMAT 0x0004 +#define PGS_DRAGNDROP 0x00000004 +#define NFS_LISTCOMBO 0x0004 +#define BCSIF_STYLE 0x0004 +#define BCSS_ALIGNLEFT 0x0004 +#define LANG_CHINESE 0x04 +#define LANG_CHINESE_SIMPLIFIED 0x04 +#define SUBLANG_CUSTOM_UNSPECIFIED 0x04 +#define SUBLANG_ARABIC_LIBYA 0x04 +#define SUBLANG_CHINESE_SINGAPORE 0x04 +#define SUBLANG_CROATIAN_BOSNIA_HERZEGOVINA_LATIN 0x04 +#define SUBLANG_ENGLISH_CAN 0x04 +#define SUBLANG_FRENCH_SWISS 0x04 +#define SUBLANG_GERMAN_LUXEMBOURG 0x04 +#define SUBLANG_SAMI_LULE_NORWAY 0x04 +#define SUBLANG_SPANISH_GUATEMALA 0x04 +#define SUBLANG_TAMAZIGHT_MOROCCO_TIFINAGH 0x04 +#define SORT_JAPANESE_RADICALSTROKE 0x4 +#define SORT_CHINESE_RADICALSTROKE 0x4 +#define VFF_BUFFTOOSMALL 0x0004 +#define SW_SHOW 5 +#define VK_XBUTTON1 0x05 +#define WM_SIZE 0x0005 +#define WMSZ_TOPRIGHT 5 +#define HTMENU 5 +#define CF_DIF 5 +#define IDIGNORE 5 +#define BN_DOUBLECLICKED 5 +#define LANG_CZECH 0x05 +#define SUBLANG_UI_CUSTOM_DEFAULT 0x05 +#define SUBLANG_ARABIC_ALGERIA 0x05 +#define SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_LATIN 0x05 +#define SUBLANG_CHINESE_MACAU 0x05 +#define SUBLANG_ENGLISH_NZ 0x05 +#define SUBLANG_FRENCH_LUXEMBOURG 0x05 +#define SUBLANG_GERMAN_LIECHTENSTEIN 0x05 +#define SUBLANG_SAMI_LULE_SWEDEN 0x05 +#define SUBLANG_SPANISH_COSTA_RICA 0x05 +#define SW_MINIMIZE 6 +#define VK_XBUTTON2 0x06 +#define WM_ACTIVATE 0x0006 +#define WMSZ_BOTTOM 6 +#define HTHSCROLL 6 +#define CF_TIFF 6 +#define IDYES 6 +#define BN_SETFOCUS 6 +#define LANG_DANISH 0x06 +#define SUBLANG_ARABIC_MOROCCO 0x06 +#define SUBLANG_ENGLISH_EIRE 0x06 +#define SUBLANG_FRENCH_MONACO 0x06 +#define SUBLANG_SAMI_SOUTHERN_NORWAY 0x06 +#define SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_LATIN 0x06 +#define SUBLANG_SPANISH_PANAMA 0x06 +#define VER_PRODUCTMAJORVERSION 6 +#define SW_SHOWMINNOACTIVE 7 +#define WM_SETFOCUS 0x0007 +#define WMSZ_BOTTOMLEFT 7 +#define HTVSCROLL 7 +#define CF_OEMTEXT 7 +#define IDNO 7 +#define BN_KILLFOCUS 7 +#define LANG_GERMAN 0x07 +#define SUBLANG_ARABIC_TUNISIA 0x07 +#define SUBLANG_ENGLISH_SOUTH_AFRICA 0x07 +#define SUBLANG_SAMI_SOUTHERN_SWEDEN 0x07 +#define SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_CYRILLIC 0x07 +#define SUBLANG_SPANISH_DOMINICAN_REPUBLIC 0x07 +#define SW_SHOWNA 8 +#define VK_BACK 0x08 +#define WM_KILLFOCUS 0x0008 +#define WMSZ_BOTTOMRIGHT 8 +#define HTMINBUTTON 8 +#define SMTO_NOTIMEOUTIFNOTHUNG 0x0008 +#define MK_CONTROL 0x0008 +#define CS_DBLCLKS 0x0008 +#define CF_DIB 8 +#define IDCLOSE 8 +#define BST_FOCUS 0x0008 +#define HDS_HIDDEN 0x0008 +#define TBSTYLE_DROPDOWN 0x0008 +#define TBS_BOTH 0x0008 +#define UDS_ALIGNLEFT 0x0008 +#define PBS_MARQUEE 0x08 +#define LWS_USEVISUALSTYLE 0x0008 +#define LVS_SHOWSELALWAYS 0x0008 +#define TVS_EDITLABELS 0x0008 +#define TVS_EX_NOINDENTSTATE 0x0008 +#define TCS_FLATBUTTONS 0x0008 +#define ACS_TIMER 0x0008 +#define MCS_NOTODAYCIRCLE 0x0008 +#define NFS_BUTTON 0x0008 +#define BCSIF_SIZE 0x0008 +#define BCSS_IMAGE 0x0008 +#define LANG_GREEK 0x08 +#define SUBLANG_ARABIC_OMAN 0x08 +#define SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_CYRILLIC 0x08 +#define SUBLANG_ENGLISH_JAMAICA 0x08 +#define SUBLANG_SAMI_SKOLT_FINLAND 0x08 +#define SUBLANG_SPANISH_VENEZUELA 0x08 +#define SW_RESTORE 9 +#define VK_TAB 0x09 +#define HTMAXBUTTON 9 +#define CF_PALETTE 9 +#define IDHELP 9 +#define DTS_TIMEFORMAT 0x0009 +#define LANG_ENGLISH 0x09 +#define SUBLANG_ARABIC_YEMEN 0x09 +#define SUBLANG_ENGLISH_CARIBBEAN 0x09 +#define SUBLANG_SAMI_INARI_FINLAND 0x09 +#define SUBLANG_SERBIAN_SERBIA_LATIN 0x09 +#define SUBLANG_SPANISH_COLOMBIA 0x09 +#define SW_SHOWDEFAULT 10 +#define WM_ENABLE 0x000A +#define HTLEFT 10 +#define CF_PENDATA 10 +#define IDTRYAGAIN 10 +#define HELP_CONTEXTMENU 0x000a +#define LANG_SPANISH 0x0a +#define SUBLANG_ARABIC_SYRIA 0x0a +#define SUBLANG_ENGLISH_BELIZE 0x0a +#define SUBLANG_SERBIAN_SERBIA_CYRILLIC 0x0a +#define SUBLANG_SPANISH_PERU 0x0a +#define SW_FORCEMINIMIZE 11 +#define SW_MAX 11 +#define WM_SETREDRAW 0x000B +#define HTRIGHT 11 +#define CF_RIFF 11 +#define IDCONTINUE 11 +#define HELP_FINDER 0x000b +#define LANG_FINNISH 0x0b +#define SUBLANG_ARABIC_JORDAN 0x0b +#define SUBLANG_ENGLISH_TRINIDAD 0x0b +#define SUBLANG_SERBIAN_MONTENEGRO_LATIN 0x0b +#define SUBLANG_SPANISH_ARGENTINA 0x0b +#define VK_CLEAR 0x0C +#define WM_SETTEXT 0x000C +#define HTTOP 12 +#define CF_WAVE 12 +#define HELP_WM_HELP 0x000c +#define DTS_SHORTDATECENTURYFORMAT 0x000C +#define LANG_FRENCH 0x0c +#define SUBLANG_ARABIC_LEBANON 0x0c +#define SUBLANG_ENGLISH_ZIMBABWE 0x0c +#define SUBLANG_SERBIAN_MONTENEGRO_CYRILLIC 0x0c +#define SUBLANG_SPANISH_ECUADOR 0x0c +#define VK_RETURN 0x0D +#define WM_GETTEXT 0x000D +#define HTTOPLEFT 13 +#define CF_UNICODETEXT 13 +#define HELP_SETPOPUP_POS 0x000d +#define LANG_HEBREW 0x0d +#define SUBLANG_ARABIC_KUWAIT 0x0d +#define SUBLANG_ENGLISH_PHILIPPINES 0x0d +#define SUBLANG_SPANISH_CHILE 0x0d +#define WM_GETTEXTLENGTH 0x000E +#define HTTOPRIGHT 14 +#define CF_ENHMETAFILE 14 +#define LANG_HUNGARIAN 0x0e +#define SUBLANG_ARABIC_UAE 0x0e +#define SUBLANG_SPANISH_URUGUAY 0x0e +#define WM_PAINT 0x000F +#define HTBOTTOM 15 +#define CF_HDROP 15 +#define LANG_ICELANDIC 0x0f +#define SUBLANG_ARABIC_BAHRAIN 0x0f +#define SUBLANG_SPANISH_PARAGUAY 0x0f +#define MAXIMUM_RESERVED_MANIFEST_RESOURCE_ID 16 +#define VK_SHIFT 0x10 +#define WM_CLOSE 0x0010 +#define HTBOTTOMLEFT 16 +#define WVR_ALIGNTOP 0x0010 +#define MK_MBUTTON 0x0010 +#define TME_NONCLIENT 0x00000010 +#define CF_LOCALE 16 +#define HELP_TCARD_DATA 0x0010 +#define TBSTYLE_AUTOSIZE 0x0010 +#define TTS_NOANIMATE 0x10 +#define TBS_NOTICKS 0x0010 +#define UDS_AUTOBUDDY 0x0010 +#define PBS_SMOOTHREVERSE 0x10 +#define LWS_USECUSTOMTEXT 0x0010 +#define LVS_SORTASCENDING 0x0010 +#define TVS_DISABLEDRAGDROP 0x0010 +#define TVS_EX_RICHTOOLTIP 0x0010 +#define TCS_FORCEICONLEFT 0x0010 +#define MCS_NOTODAY 0x0010 +#define DTS_APPCANPARSE 0x0010 +#define NFS_ALL 0x0010 +#define LANG_ITALIAN 0x10 +#define SUBLANG_ARABIC_QATAR 0x10 +#define SUBLANG_ENGLISH_INDIA 0x10 +#define SUBLANG_SPANISH_BOLIVIA 0x10 +#define VK_CONTROL 0x11 +#define WM_QUERYENDSESSION 0x0011 +#define HTBOTTOMRIGHT 17 +#define CF_DIBV5 17 +#define HELP_TCARD_OTHER_CALLER 0x0011 +#define LANG_JAPANESE 0x11 +#define SUBLANG_ENGLISH_MALAYSIA 0x11 +#define SUBLANG_SPANISH_EL_SALVADOR 0x11 +#define VK_MENU 0x12 +#define WM_QUIT 0x0012 +#define HTBORDER 18 +#define CF_MAX 18 +#define LANG_KOREAN 0x12 +#define SUBLANG_ENGLISH_SINGAPORE 0x12 +#define SUBLANG_SPANISH_HONDURAS 0x12 +#define VK_PAUSE 0x13 +#define WM_QUERYOPEN 0x0013 +#define HTOBJECT 19 +#define LANG_DUTCH 0x13 +#define SUBLANG_SPANISH_NICARAGUA 0x13 +#define VK_CAPITAL 0x14 +#define WM_ERASEBKGND 0x0014 +#define HTCLOSE 20 +#define LANG_NORWEGIAN 0x14 +#define SUBLANG_SPANISH_PUERTO_RICO 0x14 +#define _SAL_VERSION 20 +#define VK_KANA 0x15 +#define VK_HANGEUL 0x15 +#define VK_HANGUL 0x15 +#define WM_SYSCOLORCHANGE 0x0015 +#define HTHELP 21 +#define LANG_POLISH 0x15 +#define SUBLANG_SPANISH_US 0x15 +#define WM_ENDSESSION 0x0016 +#define LANG_PORTUGUESE 0x16 +#define VK_JUNJA 0x17 +#define LANG_ROMANSH 0x17 +#define RT_MANIFEST 24 +#define VK_FINAL 0x18 +#define WM_SHOWWINDOW 0x0018 +#define LANG_ROMANIAN 0x18 +#define VK_HANJA 0x19 +#define VK_KANJI 0x19 +#define LANG_RUSSIAN 0x19 +#define WM_WININICHANGE 0x001A +#define LANG_BOSNIAN 0x1a +#define LANG_CROATIAN 0x1a +#define LANG_SERBIAN 0x1a +#define VK_ESCAPE 0x1B +#define WM_DEVMODECHANGE 0x001B +#define LANG_SLOVAK 0x1b +#define VK_CONVERT 0x1C +#define WM_ACTIVATEAPP 0x001C +#define LANG_ALBANIAN 0x1c +#define VK_NONCONVERT 0x1D +#define WM_FONTCHANGE 0x001D +#define LANG_SWEDISH 0x1d +#define VK_ACCEPT 0x1E +#define WM_TIMECHANGE 0x001E +#define LANG_THAI 0x1e +#define VK_MODECHANGE 0x1F +#define WM_CANCELMODE 0x001F +#define LANG_TURKISH 0x1f +#define VK_SPACE 0x20 +#define WM_SETCURSOR 0x0020 +#define SMTO_ERRORONEXIT 0x0020 +#define WVR_ALIGNLEFT 0x0020 +#define MK_XBUTTON1 0x0020 +#define CS_OWNDC 0x0020 +#define TBSTYLE_NOPREFIX 0x0020 +#define TTS_NOFADE 0x20 +#define TBS_ENABLESELRANGE 0x0020 +#define UDS_ARROWKEYS 0x0020 +#define LWS_RIGHT 0x0020 +#define LVS_SORTDESCENDING 0x0020 +#define TVS_SHOWSELALWAYS 0x0020 +#define TVS_EX_AUTOHSCROLL 0x0020 +#define TCS_FORCELABELLEFT 0x0020 +#define DTS_RIGHTALIGN 0x0020 +#define NFS_USEFONTASSOC 0x0020 +#define LANG_URDU 0x20 +#define VK_PRIOR 0x21 +#define WM_MOUSEACTIVATE 0x0021 +#define LANG_INDONESIAN 0x21 +#define VK_NEXT 0x22 +#define WM_CHILDACTIVATE 0x0022 +#define LANG_UKRAINIAN 0x22 +#define VK_END 0x23 +#define WM_QUEUESYNC 0x0023 +#define LANG_BELARUSIAN 0x23 +#define VK_HOME 0x24 +#define WM_GETMINMAXINFO 0x0024 +#define LANG_SLOVENIAN 0x24 +#define VK_LEFT 0x25 +#define LANG_ESTONIAN 0x25 +#define VK_UP 0x26 +#define WM_PAINTICON 0x0026 +#define LANG_LATVIAN 0x26 +#define VK_RIGHT 0x27 +#define WM_ICONERASEBKGND 0x0027 +#define LANG_LITHUANIAN 0x27 +#define VK_DOWN 0x28 +#define WM_NEXTDLGCTL 0x0028 +#define LANG_TAJIK 0x28 +#define VK_SELECT 0x29 +#define LANG_FARSI 0x29 +#define LANG_PERSIAN 0x29 +#define VK_PRINT 0x2A +#define WM_SPOOLERSTATUS 0x002A +#define LANG_VIETNAMESE 0x2a +#define VK_EXECUTE 0x2B +#define WM_DRAWITEM 0x002B +#define LANG_ARMENIAN 0x2b +#define VK_SNAPSHOT 0x2C +#define WM_MEASUREITEM 0x002C +#define LANG_AZERI 0x2c +#define LANG_AZERBAIJANI 0x2c +#define VK_INSERT 0x2D +#define WM_DELETEITEM 0x002D +#define LANG_BASQUE 0x2d +#define VK_DELETE 0x2E +#define WM_VKEYTOITEM 0x002E +#define LANG_LOWER_SORBIAN 0x2e +#define LANG_UPPER_SORBIAN 0x2e +#define VK_HELP 0x2F +#define WM_CHARTOITEM 0x002F +#define LANG_MACEDONIAN 0x2f +#define WM_SETFONT 0x0030 +#define WM_GETFONT 0x0031 +#define WM_SETHOTKEY 0x0032 +#define LANG_TSWANA 0x32 +#define WM_GETHOTKEY 0x0033 +#define LANG_XHOSA 0x34 +#define LANG_ZULU 0x35 +#define LANG_AFRIKAANS 0x36 +#define WM_QUERYDRAGICON 0x0037 +#define LANG_GEORGIAN 0x37 +#define LANG_FAEROESE 0x38 +#define WM_COMPAREITEM 0x0039 +#define LANG_HINDI 0x39 +#define LANG_MALTESE 0x3a +#define LANG_SAMI 0x3b +#define LANG_IRISH 0x3c +#define WM_GETOBJECT 0x003D +#define LANG_MALAY 0x3e +#define LANG_KAZAK 0x3f +#define WVR_ALIGNBOTTOM 0x0040 +#define MK_XBUTTON2 0x0040 +#define CS_CLASSDC 0x0040 +#define HDS_DRAGDROP 0x0040 +#define BTNS_SHOWTEXT 0x0040 +#define TTS_BALLOON 0x40 +#define TBS_FIXEDLENGTH 0x0040 +#define UDS_HORZ 0x0040 +#define LVS_SHAREIMAGELISTS 0x0040 +#define TVS_RTLREADING 0x0040 +#define TVS_EX_FADEINOUTEXPANDOS 0x0040 +#define TCS_HOTTRACK 0x0040 +#define MCS_NOTRAILINGDATES 0x0040 +#define LANG_KYRGYZ 0x40 +#define WM_COMPACTING 0x0041 +#define LANG_SWAHILI 0x41 +#define LANG_TURKMEN 0x42 +#define LANG_UZBEK 0x43 +#define WM_COMMNOTIFY 0x0044 +#define LANG_TATAR 0x44 +#define LANG_BANGLA 0x45 +#define LANG_BENGALI 0x45 +#define WM_WINDOWPOSCHANGING 0x0046 +#define LANG_PUNJABI 0x46 +#define WM_WINDOWPOSCHANGED 0x0047 +#define LANG_GUJARATI 0x47 +#define WM_POWER 0x0048 +#define LANG_ODIA 0x48 +#define LANG_ORIYA 0x48 +#define LANG_TAMIL 0x49 +#define WM_COPYDATA 0x004A +#define LANG_TELUGU 0x4a +#define WM_CANCELJOURNAL 0x004B +#define LANG_KANNADA 0x4b +#define LANG_MALAYALAM 0x4c +#define LANG_ASSAMESE 0x4d +#define WM_NOTIFY 0x004E +#define LANG_MARATHI 0x4e +#define LANG_SANSKRIT 0x4f +#define WM_INPUTLANGCHANGEREQUEST 0x0050 +#define LANG_MONGOLIAN 0x50 +#define WM_INPUTLANGCHANGE 0x0051 +#define LANG_TIBETAN 0x51 +#define WM_TCARD 0x0052 +#define LANG_WELSH 0x52 +#define WM_HELP 0x0053 +#define LANG_KHMER 0x53 +#define WM_USERCHANGED 0x0054 +#define LANG_LAO 0x54 +#define WM_NOTIFYFORMAT 0x0055 +#define LANG_GALICIAN 0x56 +#define LANG_KONKANI 0x57 +#define LANG_MANIPURI 0x58 +#define LANG_SINDHI 0x59 +#define LANG_SYRIAC 0x5a +#define VK_LWIN 0x5B +#define LANG_SINHALESE 0x5b +#define VK_RWIN 0x5C +#define LANG_CHEROKEE 0x5c +#define VK_APPS 0x5D +#define LANG_INUKTITUT 0x5d +#define LANG_AMHARIC 0x5e +#define VK_SLEEP 0x5F +#define LANG_TAMAZIGHT 0x5f +#define VK_NUMPAD0 0x60 +#define LANG_KASHMIRI 0x60 +#define VK_NUMPAD1 0x61 +#define LANG_NEPALI 0x61 +#define VK_NUMPAD2 0x62 +#define LANG_FRISIAN 0x62 +#define VK_NUMPAD3 0x63 +#define LANG_PASHTO 0x63 +#define VK_NUMPAD4 0x64 +#define LANG_FILIPINO 0x64 +#define VS_USER_DEFINED 100 +#define VK_NUMPAD5 0x65 +#define LANG_DIVEHI 0x65 +#define VK_NUMPAD6 0x66 +#define VK_NUMPAD7 0x67 +#define LANG_FULAH 0x67 +#define LANG_PULAR 0x67 +#define VK_NUMPAD8 0x68 +#define LANG_HAUSA 0x68 +#define VK_NUMPAD9 0x69 +#define VK_MULTIPLY 0x6A +#define LANG_YORUBA 0x6a +#define VK_ADD 0x6B +#define LANG_QUECHUA 0x6b +#define VK_SEPARATOR 0x6C +#define LANG_SOTHO 0x6c +#define VK_SUBTRACT 0x6D +#define LANG_BASHKIR 0x6d +#define VK_DECIMAL 0x6E +#define LANG_LUXEMBOURGISH 0x6e +#define VK_DIVIDE 0x6F +#define LANG_GREENLANDIC 0x6f +#define VK_F1 0x70 +#define LANG_IGBO 0x70 +#define VK_F2 0x71 +#define VK_F3 0x72 +#define VK_F4 0x73 +#define LANG_TIGRIGNA 0x73 +#define LANG_TIGRINYA 0x73 +#define VK_F5 0x74 +#define VK_F6 0x75 +#define LANG_HAWAIIAN 0x75 +#define VK_F7 0x76 +#define VK_F8 0x77 +#define VK_F9 0x78 +#define WHEEL_DELTA 120 +#define LANG_YI 0x78 +#define VK_F10 0x79 +#define VK_F11 0x7A +#define LANG_MAPUDUNGUN 0x7a +#define VK_F12 0x7B +#define WM_CONTEXTMENU 0x007B +#define VK_F13 0x7C +#define WM_STYLECHANGING 0x007C +#define LANG_MOHAWK 0x7c +#define VK_F14 0x7D +#define WM_STYLECHANGED 0x007D +#define VK_F15 0x7E +#define WM_DISPLAYCHANGE 0x007E +#define LANG_BRETON 0x7e +#define VK_F16 0x7F +#define WM_GETICON 0x007F +#define LANG_INVARIANT 0x7f +#define VK_F17 0x80 +#define WM_SETICON 0x0080 +#define WVR_ALIGNRIGHT 0x0080 +#define CS_PARENTDC 0x0080 +#define CF_OWNERDISPLAY 0x0080 +#define HDS_FULLDRAG 0x0080 +#define BTNS_WHOLEDROPDOWN 0x0080 +#define TTS_CLOSE 0x80 +#define TBS_NOTHUMB 0x0080 +#define UDS_NOTHOUSANDS 0x0080 +#define LVS_NOLABELWRAP 0x0080 +#define TVS_NOTOOLTIPS 0x0080 +#define TVS_EX_PARTIALCHECKBOXES 0x0080 +#define TCS_VERTICAL 0x0080 +#define MCS_SHORTDAYSOFWEEK 0x0080 +#define LANG_UIGHUR 0x80 +#define VK_F18 0x81 +#define WM_NCCREATE 0x0081 +#define CF_DSPTEXT 0x0081 +#define LANG_MAORI 0x81 +#define VK_F19 0x82 +#define WM_NCDESTROY 0x0082 +#define CF_DSPBITMAP 0x0082 +#define LANG_OCCITAN 0x82 +#define VK_F20 0x83 +#define WM_NCCALCSIZE 0x0083 +#define CF_DSPMETAFILEPICT 0x0083 +#define LANG_CORSICAN 0x83 +#define VK_F21 0x84 +#define WM_NCHITTEST 0x0084 +#define LANG_ALSATIAN 0x84 +#define VK_F22 0x85 +#define WM_NCPAINT 0x0085 +#define LANG_SAKHA 0x85 +#define LANG_YAKUT 0x85 +#define VK_F23 0x86 +#define WM_NCACTIVATE 0x0086 +#define LANG_KICHE 0x86 +#define VK_F24 0x87 +#define WM_GETDLGCODE 0x0087 +#define LANG_KINYARWANDA 0x87 +#define WM_SYNCPAINT 0x0088 +#define LANG_WOLOF 0x88 +#define LANG_DARI 0x8c +#define CF_DSPENHMETAFILE 0x008E +#define VK_NUMLOCK 0x90 +#define VK_SCROLL 0x91 +#define LANG_SCOTTISH_GAELIC 0x91 +#define VK_OEM_NEC_EQUAL 0x92 +#define VK_OEM_FJ_JISHO 0x92 +#define LANG_CENTRAL_KURDISH 0x92 +#define VK_OEM_FJ_MASSHOU 0x93 +#define VK_OEM_FJ_TOUROKU 0x94 +#define VK_OEM_FJ_LOYA 0x95 +#define VK_OEM_FJ_ROYA 0x96 +#define VK_LSHIFT 0xA0 +#define WM_NCMOUSEMOVE 0x00A0 +#define VK_RSHIFT 0xA1 +#define WM_NCLBUTTONDOWN 0x00A1 +#define VK_LCONTROL 0xA2 +#define WM_NCLBUTTONUP 0x00A2 +#define VK_RCONTROL 0xA3 +#define WM_NCLBUTTONDBLCLK 0x00A3 +#define VK_LMENU 0xA4 +#define WM_NCRBUTTONDOWN 0x00A4 +#define VK_RMENU 0xA5 +#define WM_NCRBUTTONUP 0x00A5 +#define VK_BROWSER_BACK 0xA6 +#define WM_NCRBUTTONDBLCLK 0x00A6 +#define VK_BROWSER_FORWARD 0xA7 +#define WM_NCMBUTTONDOWN 0x00A7 +#define VK_BROWSER_REFRESH 0xA8 +#define WM_NCMBUTTONUP 0x00A8 +#define VK_BROWSER_STOP 0xA9 +#define WM_NCMBUTTONDBLCLK 0x00A9 +#define VK_BROWSER_SEARCH 0xAA +#define VK_BROWSER_FAVORITES 0xAB +#define WM_NCXBUTTONDOWN 0x00AB +#define VK_BROWSER_HOME 0xAC +#define WM_NCXBUTTONUP 0x00AC +#define VK_VOLUME_MUTE 0xAD +#define WM_NCXBUTTONDBLCLK 0x00AD +#define VK_VOLUME_DOWN 0xAE +#define VK_VOLUME_UP 0xAF +#define VK_MEDIA_NEXT_TRACK 0xB0 +#define EM_GETSEL 0x00B0 +#define VK_MEDIA_PREV_TRACK 0xB1 +#define EM_SETSEL 0x00B1 +#define VK_MEDIA_STOP 0xB2 +#define EM_GETRECT 0x00B2 +#define VK_MEDIA_PLAY_PAUSE 0xB3 +#define EM_SETRECT 0x00B3 +#define VK_LAUNCH_MAIL 0xB4 +#define EM_SETRECTNP 0x00B4 +#define VK_LAUNCH_MEDIA_SELECT 0xB5 +#define EM_SCROLL 0x00B5 +#define VK_LAUNCH_APP1 0xB6 +#define EM_LINESCROLL 0x00B6 +#define VK_LAUNCH_APP2 0xB7 +#define EM_SCROLLCARET 0x00B7 +#define EM_GETMODIFY 0x00B8 +#define EM_SETMODIFY 0x00B9 +#define VK_OEM_1 0xBA +#define EM_GETLINECOUNT 0x00BA +#define VK_OEM_PLUS 0xBB +#define EM_LINEINDEX 0x00BB +#define VK_OEM_COMMA 0xBC +#define EM_SETHANDLE 0x00BC +#define VK_OEM_MINUS 0xBD +#define EM_GETHANDLE 0x00BD +#define VK_OEM_PERIOD 0xBE +#define EM_GETTHUMB 0x00BE +#define VK_OEM_2 0xBF +#define VK_OEM_3 0xC0 +#define EM_LINELENGTH 0x00C1 +#define EM_REPLACESEL 0x00C2 +#define EM_GETLINE 0x00C4 +#define EM_LIMITTEXT 0x00C5 +#define EM_CANUNDO 0x00C6 +#define EM_UNDO 0x00C7 +#define EM_FMTLINES 0x00C8 +#define EM_LINEFROMCHAR 0x00C9 +#define EM_SETTABSTOPS 0x00CB +#define EM_SETPASSWORDCHAR 0x00CC +#define EM_EMPTYUNDOBUFFER 0x00CD +#define EM_GETFIRSTVISIBLELINE 0x00CE +#define EM_SETREADONLY 0x00CF +#define EM_SETWORDBREAKPROC 0x00D0 +#define EM_GETWORDBREAKPROC 0x00D1 +#define EM_GETPASSWORDCHAR 0x00D2 +#define EM_SETMARGINS 0x00D3 +#define EM_GETMARGINS 0x00D4 +#define EM_GETLIMITTEXT 0x00D5 +#define EM_POSFROMCHAR 0x00D6 +#define EM_CHARFROMPOS 0x00D7 +#define EM_SETIMESTATUS 0x00D8 +#define EM_GETIMESTATUS 0x00D9 +#define VK_OEM_4 0xDB +#define VK_OEM_5 0xDC +#define VK_OEM_6 0xDD +#define VK_OEM_7 0xDE +#define VK_OEM_8 0xDF +#define VK_OEM_AX 0xE1 +#define VK_OEM_102 0xE2 +#define VK_ICO_HELP 0xE3 +#define VK_ICO_00 0xE4 +#define VK_PROCESSKEY 0xE5 +#define VK_ICO_CLEAR 0xE6 +#define VK_PACKET 0xE7 +#define VK_OEM_RESET 0xE9 +#define VK_OEM_JUMP 0xEA +#define VK_OEM_PA1 0xEB +#define VK_OEM_PA2 0xEC +#define VK_OEM_PA3 0xED +#define VK_OEM_WSCTRL 0xEE +#define VK_OEM_CUSEL 0xEF +#define VK_OEM_ATTN 0xF0 +#define BM_GETCHECK 0x00F0 +#define VK_OEM_FINISH 0xF1 +#define BM_SETCHECK 0x00F1 +#define VK_OEM_COPY 0xF2 +#define BM_GETSTATE 0x00F2 +#define VK_OEM_AUTO 0xF3 +#define BM_SETSTATE 0x00F3 +#define VK_OEM_ENLW 0xF4 +#define BM_SETSTYLE 0x00F4 +#define VK_OEM_BACKTAB 0xF5 +#define BM_CLICK 0x00F5 +#define VK_ATTN 0xF6 +#define BM_GETIMAGE 0x00F6 +#define VK_CRSEL 0xF7 +#define BM_SETIMAGE 0x00F7 +#define VK_EXSEL 0xF8 +#define BM_SETDONTCLICK 0x00F8 +#define VK_EREOF 0xF9 +#define VK_PLAY 0xFA +#define VK_ZOOM 0xFB +#define VK_NONAME 0xFC +#define VK_PA1 0xFD +#define VK_OEM_CLEAR 0xFE +#define WM_INPUT_DEVICE_CHANGE 0x00FE +#define SUBVERSION_MASK 0x000000FF +#define WM_INPUT 0x00FF +#define WM_KEYFIRST 0x0100 +#define WM_KEYDOWN 0x0100 +#define WVR_HREDRAW 0x0100 +#define HDS_FILTERBAR 0x0100 +#define TBSTYLE_TOOLTIPS 0x0100 +#define RBS_TOOLTIPS 0x00000100 +#define TTS_USEVISUALSTYLE 0x100 +#define SBARS_SIZEGRIP 0x0100 +#define TBS_TOOLTIPS 0x0100 +#define UDS_HOTTRACK 0x0100 +#define LVS_AUTOARRANGE 0x0100 +#define TVS_CHECKBOXES 0x0100 +#define TVS_EX_EXCLUSIONCHECKBOXES 0x0100 +#define TCS_BUTTONS 0x0100 +#define MCS_NOSELCHANGEONNAV 0x0100 +#define WM_KEYUP 0x0101 +#define WM_CHAR 0x0102 +#define WM_DEADCHAR 0x0103 +#define WM_SYSKEYDOWN 0x0104 +#define WM_SYSKEYUP 0x0105 +#define WM_SYSCHAR 0x0106 +#define WM_SYSDEADCHAR 0x0107 +#define WM_UNICHAR 0x0109 +#define WM_KEYLAST 0x0109 +#define WM_IME_STARTCOMPOSITION 0x010D +#define WM_IME_ENDCOMPOSITION 0x010E +#define WM_IME_COMPOSITION 0x010F +#define WM_IME_KEYLAST 0x010F +#define WM_INITDIALOG 0x0110 +#define WM_COMMAND 0x0111 +#define WM_SYSCOMMAND 0x0112 +#define WM_TIMER 0x0113 +#define WM_HSCROLL 0x0114 +#define WM_VSCROLL 0x0115 +#define WM_INITMENU 0x0116 +#define WM_INITMENUPOPUP 0x0117 +#define WM_GESTURE 0x0119 +#define WM_GESTURENOTIFY 0x011A +#define WM_MENUSELECT 0x011F +#define WM_MENUCHAR 0x0120 +#define WM_ENTERIDLE 0x0121 +#define WM_MENURBUTTONUP 0x0122 +#define WM_MENUDRAG 0x0123 +#define WM_MENUGETOBJECT 0x0124 +#define WM_UNINITMENUPOPUP 0x0125 +#define WM_MENUCOMMAND 0x0126 +#define WM_CHANGEUISTATE 0x0127 +#define WM_UPDATEUISTATE 0x0128 +#define WM_QUERYUISTATE 0x0129 +#define WM_CTLCOLORMSGBOX 0x0132 +#define WM_CTLCOLOREDIT 0x0133 +#define WM_CTLCOLORLISTBOX 0x0134 +#define WM_CTLCOLORBTN 0x0135 +#define WM_CTLCOLORDLG 0x0136 +#define WM_CTLCOLORSCROLLBAR 0x0137 +#define WM_CTLCOLORSTATIC 0x0138 +#define MN_GETHMENU 0x01E1 +#define _WIN32_IE_IE20 0x0200 +#define WM_MOUSEFIRST 0x0200 +#define WM_MOUSEMOVE 0x0200 +#define WVR_VREDRAW 0x0200 +#define CS_NOCLOSE 0x0200 +#define CF_PRIVATEFIRST 0x0200 +#define HDS_FLAT 0x0200 +#define TBSTYLE_WRAPABLE 0x0200 +#define RBS_VARHEIGHT 0x00000200 +#define TBS_REVERSED 0x0200 +#define LVS_EDITLABELS 0x0200 +#define TVS_TRACKSELECT 0x0200 +#define TVS_EX_DIMMEDCHECKBOXES 0x0200 +#define TCS_MULTILINE 0x0200 +#define WM_LBUTTONDOWN 0x0201 +#define WM_LBUTTONUP 0x0202 +#define WM_LBUTTONDBLCLK 0x0203 +#define WM_RBUTTONDOWN 0x0204 +#define WM_RBUTTONUP 0x0205 +#define WM_RBUTTONDBLCLK 0x0206 +#define WM_MBUTTONDOWN 0x0207 +#define WM_MBUTTONUP 0x0208 +#define WM_MBUTTONDBLCLK 0x0209 +#define WM_MOUSEWHEEL 0x020A +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#define WM_MOUSEHWHEEL 0x020E +#define WM_MOUSELAST 0x020E +#define WM_PARENTNOTIFY 0x0210 +#define WM_ENTERMENULOOP 0x0211 +#define WM_EXITMENULOOP 0x0212 +#define WM_NEXTMENU 0x0213 +#define WM_SIZING 0x0214 +#define WM_CAPTURECHANGED 0x0215 +#define WM_MOVING 0x0216 +#define WM_POWERBROADCAST 0x0218 +#define WM_DEVICECHANGE 0x0219 +#define WM_MDICREATE 0x0220 +#define WM_MDIDESTROY 0x0221 +#define WM_MDIACTIVATE 0x0222 +#define WM_MDIRESTORE 0x0223 +#define WM_MDINEXT 0x0224 +#define WM_MDIMAXIMIZE 0x0225 +#define WM_MDITILE 0x0226 +#define WM_MDICASCADE 0x0227 +#define WM_MDIICONARRANGE 0x0228 +#define WM_MDIGETACTIVE 0x0229 +#define WM_MDISETMENU 0x0230 +#define WM_ENTERSIZEMOVE 0x0231 +#define WM_EXITSIZEMOVE 0x0232 +#define WM_DROPFILES 0x0233 +#define WM_MDIREFRESHMENU 0x0234 +#define WM_POINTERDEVICECHANGE 0x238 +#define WM_POINTERDEVICEINRANGE 0x239 +#define WM_POINTERDEVICEOUTOFRANGE 0x23A +#define WM_TOUCH 0x0240 +#define WM_NCPOINTERUPDATE 0x0241 +#define WM_NCPOINTERDOWN 0x0242 +#define WM_NCPOINTERUP 0x0243 +#define WM_POINTERUPDATE 0x0245 +#define WM_POINTERDOWN 0x0246 +#define WM_POINTERUP 0x0247 +#define WM_POINTERENTER 0x0249 +#define WM_POINTERLEAVE 0x024A +#define WM_POINTERACTIVATE 0x024B +#define WM_POINTERCAPTURECHANGED 0x024C +#define WM_TOUCHHITTESTING 0x024D +#define WM_POINTERWHEEL 0x024E +#define WM_POINTERHWHEEL 0x024F +#define WM_IME_SETCONTEXT 0x0281 +#define WM_IME_NOTIFY 0x0282 +#define WM_IME_CONTROL 0x0283 +#define WM_IME_COMPOSITIONFULL 0x0284 +#define WM_IME_SELECT 0x0285 +#define WM_IME_CHAR 0x0286 +#define WM_IME_REQUEST 0x0288 +#define WM_IME_KEYDOWN 0x0290 +#define WM_IME_KEYUP 0x0291 +#define WM_NCMOUSEHOVER 0x02A0 +#define WM_MOUSEHOVER 0x02A1 +#define WM_NCMOUSELEAVE 0x02A2 +#define WM_MOUSELEAVE 0x02A3 +#define WM_WTSSESSION_CHANGE 0x02B1 +#define WM_TABLET_FIRST 0x02c0 +#define WM_TABLET_LAST 0x02df +#define CF_PRIVATELAST 0x02FF +#define _WIN32_IE_IE30 0x0300 +#define WM_CUT 0x0300 +#define CF_GDIOBJFIRST 0x0300 +#define WM_COPY 0x0301 +#define _WIN32_IE_IE302 0x0302 +#define WM_PASTE 0x0302 +#define WM_CLEAR 0x0303 +#define WM_UNDO 0x0304 +#define WM_RENDERFORMAT 0x0305 +#define WM_RENDERALLFORMATS 0x0306 +#define WM_DESTROYCLIPBOARD 0x0307 +#define WM_DRAWCLIPBOARD 0x0308 +#define WM_PAINTCLIPBOARD 0x0309 +#define WM_VSCROLLCLIPBOARD 0x030A +#define WM_SIZECLIPBOARD 0x030B +#define WM_ASKCBFORMATNAME 0x030C +#define WM_CHANGECBCHAIN 0x030D +#define WM_HSCROLLCLIPBOARD 0x030E +#define WM_QUERYNEWPALETTE 0x030F +#define WM_PALETTEISCHANGING 0x0310 +#define WM_PALETTECHANGED 0x0311 +#define WM_HOTKEY 0x0312 +#define WM_PRINT 0x0317 +#define WM_PRINTCLIENT 0x0318 +#define WM_APPCOMMAND 0x0319 +#define WM_THEMECHANGED 0x031A +#define WM_CLIPBOARDUPDATE 0x031D +#define WM_DWMCOMPOSITIONCHANGED 0x031E +#define WM_DWMNCRENDERINGCHANGED 0x031F +#define WM_DWMCOLORIZATIONCOLORCHANGED 0x0320 +#define WM_DWMWINDOWMAXIMIZEDCHANGE 0x0321 +#define WM_DWMSENDICONICTHUMBNAIL 0x0323 +#define WM_DWMSENDICONICLIVEPREVIEWBITMAP 0x0326 +#define WM_GETTITLEBARINFOEX 0x033F +#define WM_HANDHELDFIRST 0x0358 +#define WM_HANDHELDLAST 0x035F +#define WM_AFXFIRST 0x0360 +#define WM_AFXLAST 0x037F +#define WM_PENWINFIRST 0x0380 +#define WM_PENWINLAST 0x038F +#define WM_DDE_FIRST 0x03E0 +#define CF_GDIOBJLAST 0x03FF +#define _WIN32_WINNT_NT4 0x0400 +#define _WIN32_IE_IE40 0x0400 +#define WM_USER 0x0400 +#define WVR_VALIDRECTS 0x0400 +#define HDS_CHECKBOXES 0x0400 +#define TBSTYLE_ALTDRAG 0x0400 +#define RBS_BANDBORDERS 0x00000400 +#define TBS_DOWNISLEFT 0x0400 +#define LVS_OWNERDRAWFIXED 0x0400 +#define TVS_SINGLEEXPAND 0x0400 +#define TVS_EX_DRAWIMAGEASYNC 0x0400 +#define TCS_FIXEDWIDTH 0x0400 +#define ctlFirst 0x0400 +#define psh1 0x0400 +#define _WIN32_IE_IE401 0x0401 +#define psh2 0x0401 +#define psh3 0x0402 +#define psh4 0x0403 +#define psh5 0x0404 +#define psh6 0x0405 +#define psh7 0x0406 +#define psh8 0x0407 +#define psh9 0x0408 +#define psh10 0x0409 +#define psh11 0x040a +#define psh12 0x040b +#define psh13 0x040c +#define psh14 0x040d +#define psh15 0x040e +#define psh16 0x040f +#define _WIN32_WINDOWS 0x0410 +#define chx1 0x0410 +#define chx2 0x0411 +#define chx3 0x0412 +#define chx4 0x0413 +#define chx5 0x0414 +#define chx6 0x0415 +#define chx7 0x0416 +#define chx8 0x0417 +#define chx9 0x0418 +#define chx10 0x0419 +#define chx11 0x041a +#define chx12 0x041b +#define chx13 0x041c +#define chx14 0x041d +#define chx15 0x041e +#define chx16 0x041f +#define rad1 0x0420 +#define rad2 0x0421 +#define rad3 0x0422 +#define rad4 0x0423 +#define rad5 0x0424 +#define rad6 0x0425 +#define rad7 0x0426 +#define rad8 0x0427 +#define rad9 0x0428 +#define rad10 0x0429 +#define rad11 0x042a +#define rad12 0x042b +#define rad13 0x042c +#define rad14 0x042d +#define rad15 0x042e +#define rad16 0x042f +#define grp1 0x0430 +#define grp2 0x0431 +#define grp3 0x0432 +#define grp4 0x0433 +#define frm1 0x0434 +#define frm2 0x0435 +#define frm3 0x0436 +#define frm4 0x0437 +#define rct1 0x0438 +#define rct2 0x0439 +#define rct3 0x043a +#define rct4 0x043b +#define ico1 0x043c +#define ico2 0x043d +#define ico3 0x043e +#define ico4 0x043f +#define stc1 0x0440 +#define stc2 0x0441 +#define stc3 0x0442 +#define stc4 0x0443 +#define stc5 0x0444 +#define stc6 0x0445 +#define stc7 0x0446 +#define stc8 0x0447 +#define stc9 0x0448 +#define stc10 0x0449 +#define stc11 0x044a +#define stc12 0x044b +#define stc13 0x044c +#define stc14 0x044d +#define stc15 0x044e +#define stc16 0x044f +#define stc17 0x0450 +#define stc18 0x0451 +#define stc19 0x0452 +#define stc20 0x0453 +#define stc21 0x0454 +#define stc22 0x0455 +#define stc23 0x0456 +#define stc24 0x0457 +#define stc25 0x0458 +#define stc26 0x0459 +#define stc27 0x045a +#define stc28 0x045b +#define stc29 0x045c +#define stc30 0x045d +#define stc31 0x045e +#define stc32 0x045f +#define lst1 0x0460 +#define lst2 0x0461 +#define lst3 0x0462 +#define lst4 0x0463 +#define lst5 0x0464 +#define lst6 0x0465 +#define lst7 0x0466 +#define lst8 0x0467 +#define lst9 0x0468 +#define lst10 0x0469 +#define lst11 0x046a +#define lst12 0x046b +#define lst13 0x046c +#define lst14 0x046d +#define lst15 0x046e +#define lst16 0x046f +#define cmb1 0x0470 +#define cmb2 0x0471 +#define cmb3 0x0472 +#define cmb4 0x0473 +#define cmb5 0x0474 +#define cmb6 0x0475 +#define cmb7 0x0476 +#define cmb8 0x0477 +#define cmb9 0x0478 +#define cmb10 0x0479 +#define cmb11 0x047a +#define cmb12 0x047b +#define cmb13 0x047c +#define cmb14 0x047d +#define cmb15 0x047e +#define cmb16 0x047f +#define edt1 0x0480 +#define edt2 0x0481 +#define edt3 0x0482 +#define edt4 0x0483 +#define edt5 0x0484 +#define edt6 0x0485 +#define edt7 0x0486 +#define edt8 0x0487 +#define edt9 0x0488 +#define edt10 0x0489 +#define edt11 0x048a +#define edt12 0x048b +#define edt13 0x048c +#define edt14 0x048d +#define edt15 0x048e +#define edt16 0x048f +#define scr1 0x0490 +#define scr2 0x0491 +#define scr3 0x0492 +#define scr4 0x0493 +#define scr5 0x0494 +#define scr6 0x0495 +#define scr7 0x0496 +#define scr8 0x0497 +#define ctl1 0x04A0 +#define ctlLast 0x04ff +#define _WIN32_WINNT_WIN2K 0x0500 +#define _WIN32_IE_IE50 0x0500 +#define _WIN32_WINNT_WINXP 0x0501 +#define _WIN32_IE_IE501 0x0501 +#define _WIN32_WINNT_WS03 0x0502 +#define _WIN32_IE_IE55 0x0550 +#define _WIN32_WINNT_WIN6 0x0600 +#define _WIN32_WINNT_VISTA 0x0600 +#define _WIN32_WINNT_WS08 0x0600 +#define _WIN32_WINNT_LONGHORN 0x0600 +#define _WIN32_IE_IE60 0x0600 +#define FILEOPENORD 1536 +#define _WIN32_WINNT_WIN7 0x0601 +#define _WIN32_IE_IE60SP1 0x0601 +#define MULTIFILEOPENORD 1537 +#define _WIN32_WINNT_WIN8 0x0602 +#define _WIN32_IE_WS03 0x0602 +#define _WIN32_WINNT 0x0600 +#define PRINTDLGORD 1538 +#define VER_PRODUCTVERSION_W 0x0602 +#define _WIN32_IE_IE60SP2 0x0603 +#define PRNSETUPDLGORD 1539 +#define FINDDLGORD 1540 +#define REPLACEDLGORD 1541 +#define FONTDLGORD 1542 +#define FORMATDLGORD31 1543 +#define FORMATDLGORD30 1544 +#define RUNDLGORD 1545 +#define PAGESETUPDLGORD 1546 +#define NEWFILEOPENORD 1547 +#define PRINTDLGEXORD 1549 +#define PAGESETUPDLGORDMOTIF 1550 +#define COLORMGMTDLGORD 1551 +#define NEWFILEOPENV2ORD 1552 +#define NEWFILEOPENV3ORD 1553 +#define NEWFORMATDLGWITHLINK 1591 +#define IDC_MANAGE_LINK 1592 +#define _WIN32_IE_IE70 0x0700 +#define _WIN32_IE_IE80 0x0800 +#define CS_SAVEBITS 0x0800 +#define HDS_NOSIZING 0x0800 +#define TBSTYLE_FLAT 0x0800 +#define RBS_FIXEDORDER 0x00000800 +#define SBARS_TOOLTIPS 0x0800 +#define SBT_TOOLTIPS 0x0800 +#define TBS_NOTIFYBEFOREMOVE 0x0800 +#define LVS_ALIGNLEFT 0x0800 +#define TVS_INFOTIP 0x0800 +#define TCS_RAGGEDRIGHT 0x0800 +#define _WIN32_IE_IE90 0x0900 +#define _WIN32_IE_IE100 0x0A00 +#define LVS_ALIGNMASK 0x0c00 +#define CS_BYTEALIGNCLIENT 0x1000 +#define HDS_OVERFLOW 0x1000 +#define TBSTYLE_LIST 0x1000 +#define RBS_REGISTERDROP 0x00001000 +#define TBS_TRANSPARENTBKGND 0x1000 +#define LVS_OWNERDATA 0x1000 +#define TVS_FULLROWSELECT 0x1000 +#define TCS_FOCUSONBUTTONDOWN 0x1000 +#define CS_BYTEALIGNWINDOW 0x2000 +#define TBSTYLE_CUSTOMERASE 0x2000 +#define RBS_AUTOSIZE 0x00002000 +#define LVS_NOSCROLL 0x2000 +#define TVS_NOSCROLL 0x2000 +#define TCS_OWNERDRAWFIXED 0x2000 +#define VER_PRODUCTBUILD 9200 +#define CS_GLOBALCLASS 0x4000 +#define TBSTYLE_REGISTERDROP 0x4000 +#define RBS_VERTICALGRIPPER 0x00004000 +#define LVS_NOCOLUMNHEADER 0x4000 +#define TVS_NONEVENHEIGHT 0x4000 +#define TCS_TOOLTIPS 0x4000 +#define VER_PRODUCTBUILD_QFE 20557 +#define VER_PACKAGEBUILD_QFE 20557 +#define IDH_NO_HELP 28440 +#define IDH_MISSING_CONTEXT 28441 +#define IDH_GENERIC_HELP_BUTTON 28442 +#define IDH_OK 28443 +#define IDH_CANCEL 28444 +#define IDH_HELP 28445 +#define LANG_BOSNIAN_NEUTRAL 0x781a +#define LANG_CHINESE_TRADITIONAL 0x7c04 +#define LANG_SERBIAN_NEUTRAL 0x7c1a +#define IDTIMEOUT 32000 +#define OCR_NORMAL 32512 +#define OIC_SAMPLE 32512 +#define IDI_APPLICATION 32512 +#define OCR_IBEAM 32513 +#define OIC_HAND 32513 +#define IDI_HAND 32513 +#define OCR_WAIT 32514 +#define OIC_QUES 32514 +#define IDI_QUESTION 32514 +#define OCR_CROSS 32515 +#define OIC_BANG 32515 +#define IDI_EXCLAMATION 32515 +#define OCR_UP 32516 +#define OIC_NOTE 32516 +#define IDI_ASTERISK 32516 +#define OIC_WINLOGO 32517 +#define IDI_WINLOGO 32517 +#define OIC_SHIELD 32518 +#define IDI_SHIELD 32518 +#define OCR_SIZE 32640 +#define OCR_ICON 32641 +#define OCR_SIZENWSE 32642 +#define OCR_SIZENESW 32643 +#define OCR_SIZEWE 32644 +#define OCR_SIZENS 32645 +#define OCR_SIZEALL 32646 +#define OCR_ICOCUR 32647 +#define OCR_NO 32648 +#define OCR_HAND 32649 +#define OCR_APPSTARTING 32650 +#define OBM_LFARROWI 32734 +#define OBM_RGARROWI 32735 +#define OBM_DNARROWI 32736 +#define OBM_UPARROWI 32737 +#define OBM_COMBO 32738 +#define OBM_MNARROW 32739 +#define OBM_LFARROWD 32740 +#define OBM_RGARROWD 32741 +#define OBM_DNARROWD 32742 +#define OBM_UPARROWD 32743 +#define OBM_RESTORED 32744 +#define OBM_ZOOMD 32745 +#define OBM_REDUCED 32746 +#define OBM_RESTORE 32747 +#define OBM_ZOOM 32748 +#define OBM_REDUCE 32749 +#define OBM_LFARROW 32750 +#define OBM_RGARROW 32751 +#define OBM_DNARROW 32752 +#define OBM_UPARROW 32753 +#define OBM_CLOSE 32754 +#define OBM_OLD_RESTORE 32755 +#define OBM_OLD_ZOOM 32756 +#define OBM_OLD_REDUCE 32757 +#define OBM_BTNCORNERS 32758 +#define OBM_CHECKBOXES 32759 +#define OBM_CHECK 32760 +#define OBM_BTSIZE 32761 +#define OBM_OLD_LFARROW 32762 +#define OBM_OLD_RGARROW 32763 +#define OBM_OLD_DNARROW 32764 +#define OBM_OLD_UPARROW 32765 +#define OBM_SIZE 32766 +#define OBM_OLD_CLOSE 32767 +#define WM_APP 0x8000 +#define HELP_TCARD 0x8000 +#define TBSTYLE_TRANSPARENT 0x8000 +#define RBS_DBLCLKTOGGLE 0x00008000 +#define LVS_NOSORTHEADER 0x8000 +#define TVS_NOHSCROLL 0x8000 +#define TCS_FOCUSNEVER 0x8000 +#define SC_SIZE 0xF000 +#define SC_SEPARATOR 0xF00F +#define SC_MOVE 0xF010 +#define SC_MINIMIZE 0xF020 +#define SC_MAXIMIZE 0xF030 +#define SC_NEXTWINDOW 0xF040 +#define SC_PREVWINDOW 0xF050 +#define SC_CLOSE 0xF060 +#define SC_VSCROLL 0xF070 +#define SC_HSCROLL 0xF080 +#define SC_MOUSEMENU 0xF090 +#define SC_KEYMENU 0xF100 +#define SC_ARRANGE 0xF110 +#define SC_RESTORE 0xF120 +#define SC_TASKLIST 0xF130 +#define SC_SCREENSAVE 0xF140 +#define SC_HOTKEY 0xF150 +#define SC_DEFAULT 0xF160 +#define SC_MONITORPOWER 0xF170 +#define SC_CONTEXTHELP 0xF180 +#define LVS_TYPESTYLEMASK 0xfc00 +#define SPVERSION_MASK 0x0000FF00 +#define HTERROR -2 +#define PWR_FAIL -1 +#define UNICODE_NOCHAR 0xFFFF +#define HTTRANSPARENT -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/TapDriver6/resource.rc b/windows/TapDriver6/resource.rc new file mode 100644 index 0000000..2b65e2b --- /dev/null +++ b/windows/TapDriver6/resource.rc @@ -0,0 +1,88 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 3,0,0,0 + PRODUCTVERSION 3,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x9L +#else + FILEFLAGS 0x8L +#endif + FILEOS 0x40004L + FILETYPE 0x3L + FILESUBTYPE 0x6L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "ZeroTier Networks LLC" + VALUE "FileDescription", "ZeroTier One Virtual Network Port" + VALUE "FileVersion", "3.0.0 3/0" + VALUE "InternalName", "zttap300.sys" + VALUE "LegalCopyright", "ZeroTier, Inc., OpenVPN Technologies, Inc." + VALUE "OriginalFilename", "zttap300.sys" + VALUE "ProductName", "ZeroTier One Virtual Network Port" + VALUE "ProductVersion", "3.0.0 3/0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/windows/TapDriver6/rxpath.c b/windows/TapDriver6/rxpath.c new file mode 100644 index 0000000..318bc56 --- /dev/null +++ b/windows/TapDriver6/rxpath.c @@ -0,0 +1,669 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// +// Include files. +// + +#include "tap.h" + +//====================================================================== +// TAP Receive Path Support +//====================================================================== + +#ifdef ALLOC_PRAGMA +#pragma alloc_text( PAGE, TapDeviceWrite) +#endif // ALLOC_PRAGMA + +//=============================================================== +// Used in cases where internally generated packets such as +// ARP or DHCP replies must be returned to the kernel, to be +// seen as an incoming packet "arriving" on the interface. +//=============================================================== + +VOID +IndicateReceivePacket( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in PUCHAR packetData, + __in const unsigned int packetLength + ) +{ + PUCHAR injectBuffer; + + // + // Handle miniport Pause + // --------------------- + // NDIS 6 miniports implement a temporary "Pause" state normally followed + // by the Restart. While in the Pause state it is forbidden for the miniport + // to indicate receive NBLs. + // + // That is: The device interface may be "up", but the NDIS miniport send/receive + // interface may be temporarily "down". + // + // BUGBUG!!! In the initial implementation of the NDIS 6 TapOas inject path + // the code below will simply ignore inject packets passed to the driver while + // the miniport is in the Paused state. + // + // The correct implementation is to go ahead and build the NBLs corresponding + // to the inject packet - but queue them. When Restart is entered the + // queued NBLs would be dequeued and indicated to the host. + // + if(tapAdapterSendAndReceiveReady(Adapter) != NDIS_STATUS_SUCCESS) + { + DEBUGP (("[%s] Lying send in IndicateReceivePacket while adapter paused\n", + MINIPORT_INSTANCE_ID (Adapter))); + + return; + } + + // Allocate flat buffer for packet data. + injectBuffer = (PUCHAR )NdisAllocateMemoryWithTagPriority( + Adapter->MiniportAdapterHandle, + packetLength, + TAP_RX_INJECT_BUFFER_TAG, + NormalPoolPriority + ); + + if( injectBuffer) + { + PMDL mdl; + + // Copy packet data to flat buffer. + NdisMoveMemory (injectBuffer, packetData, packetLength); + + // Allocate MDL for flat buffer. + mdl = NdisAllocateMdl( + Adapter->MiniportAdapterHandle, + injectBuffer, + packetLength + ); + + if( mdl ) + { + PNET_BUFFER_LIST netBufferList; + + mdl->Next = NULL; // No next MDL + + // Allocate the NBL and NB. Link MDL chain to NB. + netBufferList = NdisAllocateNetBufferAndNetBufferList( + Adapter->ReceiveNblPool, + 0, // ContextSize + 0, // ContextBackFill + mdl, // MDL chain + 0, + packetLength + ); + + if(netBufferList != NULL) + { + ULONG receiveFlags = 0; + LONG nblCount; + + NET_BUFFER_LIST_NEXT_NBL(netBufferList) = NULL; // Only one NBL + + if(KeGetCurrentIrql() == DISPATCH_LEVEL) + { + receiveFlags |= NDIS_RECEIVE_FLAGS_DISPATCH_LEVEL; + } + + // Set flag indicating that this is an injected packet + TAP_RX_NBL_FLAGS_CLEAR_ALL(netBufferList); + TAP_RX_NBL_FLAG_SET(netBufferList,TAP_RX_NBL_FLAGS_IS_INJECTED); + + netBufferList->MiniportReserved[0] = NULL; + netBufferList->MiniportReserved[1] = NULL; + + // Increment in-flight receive NBL count. + nblCount = NdisInterlockedIncrement(&Adapter->ReceiveNblInFlightCount); + ASSERT(nblCount > 0 ); + + netBufferList->SourceHandle = Adapter->MiniportAdapterHandle; + + // + // Indicate the packet + // ------------------- + // Irp->AssociatedIrp.SystemBuffer with length irpSp->Parameters.Write.Length + // contains the complete packet including Ethernet header and payload. + // + NdisMIndicateReceiveNetBufferLists( + Adapter->MiniportAdapterHandle, + netBufferList, + NDIS_DEFAULT_PORT_NUMBER, + 1, // NumberOfNetBufferLists + receiveFlags + ); + + return; + } + else + { + DEBUGP (("[%s] NdisAllocateNetBufferAndNetBufferList failed in IndicateReceivePacket\n", + MINIPORT_INSTANCE_ID (Adapter))); + NOTE_ERROR (); + + NdisFreeMdl(mdl); + NdisFreeMemory(injectBuffer,0,0); + } + } + else + { + DEBUGP (("[%s] NdisAllocateMdl failed in IndicateReceivePacket\n", + MINIPORT_INSTANCE_ID (Adapter))); + NOTE_ERROR (); + + NdisFreeMemory(injectBuffer,0,0); + } + } + else + { + DEBUGP (("[%s] NdisAllocateMemoryWithTagPriority failed in IndicateReceivePacket\n", + MINIPORT_INSTANCE_ID (Adapter))); + NOTE_ERROR (); + } +} + +VOID +tapCompleteIrpAndFreeReceiveNetBufferList( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in PNET_BUFFER_LIST NetBufferList, // Only one NB here... + __in NTSTATUS IoCompletionStatus + ) +{ + PIRP irp; + ULONG frameType, netBufferCount, byteCount; + LONG nblCount; + + // Fetch NB frame type. + frameType = tapGetNetBufferFrameType(NET_BUFFER_LIST_FIRST_NB(NetBufferList)); + + // Fetch statistics for all NBs linked to the NB. + netBufferCount = tapGetNetBufferCountsFromNetBufferList( + NetBufferList, + &byteCount + ); + + // Update statistics by frame type + if(IoCompletionStatus == STATUS_SUCCESS) + { + switch(frameType) + { + case NDIS_PACKET_TYPE_DIRECTED: + Adapter->FramesRxDirected += netBufferCount; + Adapter->BytesRxDirected += byteCount; + break; + + case NDIS_PACKET_TYPE_BROADCAST: + Adapter->FramesRxBroadcast += netBufferCount; + Adapter->BytesRxBroadcast += byteCount; + break; + + case NDIS_PACKET_TYPE_MULTICAST: + Adapter->FramesRxMulticast += netBufferCount; + Adapter->BytesRxMulticast += byteCount; + break; + + default: + ASSERT(FALSE); + break; + } + } + + // + // Handle P2P Packet + // ----------------- + // Free MDL allocated for P2P Ethernet header. + // + if(TAP_RX_NBL_FLAG_TEST(NetBufferList,TAP_RX_NBL_FLAGS_IS_P2P)) + { + PNET_BUFFER netBuffer; + PMDL mdl; + + netBuffer = NET_BUFFER_LIST_FIRST_NB(NetBufferList); + mdl = NET_BUFFER_FIRST_MDL(netBuffer); + mdl->Next = NULL; + + NdisFreeMdl(mdl); + } + + // + // Handle Injected Packet + // ----------------------- + // Free MDL and data buffer allocated for injected packet. + // + if(TAP_RX_NBL_FLAG_TEST(NetBufferList,TAP_RX_NBL_FLAGS_IS_INJECTED)) + { + PNET_BUFFER netBuffer; + PMDL mdl; + PUCHAR injectBuffer; + + netBuffer = NET_BUFFER_LIST_FIRST_NB(NetBufferList); + mdl = NET_BUFFER_FIRST_MDL(netBuffer); + + injectBuffer = (PUCHAR )MmGetSystemAddressForMdlSafe(mdl,NormalPagePriority); + + if(injectBuffer) + { + NdisFreeMemory(injectBuffer,0,0); + } + + NdisFreeMdl(mdl); + } + + // + // Complete the IRP + // + irp = (PIRP )NetBufferList->MiniportReserved[0]; + + if(irp) + { + irp->IoStatus.Status = IoCompletionStatus; + IoCompleteRequest(irp, IO_NO_INCREMENT); + } + + // Decrement in-flight receive NBL count. + nblCount = NdisInterlockedDecrement(&Adapter->ReceiveNblInFlightCount); + ASSERT(nblCount >= 0 ); + if (0 == nblCount) + { + NdisSetEvent(&Adapter->ReceiveNblInFlightCountZeroEvent); + } + + // Free the NBL + NdisFreeNetBufferList(NetBufferList); +} + +VOID +AdapterReturnNetBufferLists( + __in NDIS_HANDLE MiniportAdapterContext, + __in PNET_BUFFER_LIST NetBufferLists, + __in ULONG ReturnFlags + ) +{ + PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; + PNET_BUFFER_LIST currentNbl, nextNbl; + + UNREFERENCED_PARAMETER(ReturnFlags); + + // + // Process each NBL individually + // + currentNbl = NetBufferLists; + while (currentNbl) + { + PNET_BUFFER_LIST nextNbl; + + nextNbl = NET_BUFFER_LIST_NEXT_NBL(currentNbl); + NET_BUFFER_LIST_NEXT_NBL(currentNbl) = NULL; + + // Complete write IRP and free NBL and associated resources. + tapCompleteIrpAndFreeReceiveNetBufferList( + adapter, + currentNbl, + STATUS_SUCCESS + ); + + // Move to next NBL + currentNbl = nextNbl; + } +} + +// IRP_MJ_WRITE callback. +NTSTATUS +TapDeviceWrite( + PDEVICE_OBJECT DeviceObject, + PIRP Irp + ) +{ + NTSTATUS ntStatus = STATUS_SUCCESS;// Assume success + PIO_STACK_LOCATION irpSp;// Pointer to current stack location + PTAP_ADAPTER_CONTEXT adapter = NULL; + ULONG dataLength; + + PAGED_CODE(); + + irpSp = IoGetCurrentIrpStackLocation( Irp ); + + // + // Fetch adapter context for this device. + // -------------------------------------- + // Adapter pointer was stashed in FsContext when handle was opened. + // + adapter = (PTAP_ADAPTER_CONTEXT )(irpSp->FileObject)->FsContext; + + ASSERT(adapter); + + // + // Sanity checks on state variables + // + if (!tapAdapterReadAndWriteReady(adapter)) + { + //DEBUGP (("[%s] Interface is down in IRP_MJ_WRITE\n", + // MINIPORT_INSTANCE_ID (adapter))); + //NOTE_ERROR(); + + Irp->IoStatus.Status = ntStatus = STATUS_CANCELLED; + Irp->IoStatus.Information = 0; + IoCompleteRequest (Irp, IO_NO_INCREMENT); + + return ntStatus; + } + + // Save IRP-accessible copy of buffer length + Irp->IoStatus.Information = irpSp->Parameters.Write.Length; + + if (Irp->MdlAddress == NULL) + { + DEBUGP (("[%s] MdlAddress is NULL for IRP_MJ_WRITE\n", + MINIPORT_INSTANCE_ID (adapter))); + + NOTE_ERROR(); + Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER; + Irp->IoStatus.Information = 0; + IoCompleteRequest (Irp, IO_NO_INCREMENT); + + return ntStatus; + } + + // + // Try to get a virtual address for the MDL. + // + NdisQueryMdl( + Irp->MdlAddress, + &Irp->AssociatedIrp.SystemBuffer, + &dataLength, + NormalPagePriority + ); + + if (Irp->AssociatedIrp.SystemBuffer == NULL) + { + DEBUGP (("[%s] Could not map address in IRP_MJ_WRITE\n", + MINIPORT_INSTANCE_ID (adapter))); + + NOTE_ERROR(); + Irp->IoStatus.Status = ntStatus = STATUS_INSUFFICIENT_RESOURCES; + Irp->IoStatus.Information = 0; + IoCompleteRequest (Irp, IO_NO_INCREMENT); + + return ntStatus; + } + + ASSERT(dataLength == irpSp->Parameters.Write.Length); + + Irp->IoStatus.Information = irpSp->Parameters.Write.Length; + + // + // Handle miniport Pause + // --------------------- + // NDIS 6 miniports implement a temporary "Pause" state normally followed + // by the Restart. While in the Pause state it is forbidden for the miniport + // to indicate receive NBLs. + // + // That is: The device interface may be "up", but the NDIS miniport send/receive + // interface may be temporarily "down". + // + // BUGBUG!!! In the initial implementation of the NDIS 6 TapOas receive path + // the code below will perform a "lying send" for write IRPs passed to the + // driver while the miniport is in the Paused state. + // + // The correct implementation is to go ahead and build the NBLs corresponding + // to the user-mode write - but queue them. When Restart is entered the + // queued NBLs would be dequeued and indicated to the host. + // + if(tapAdapterSendAndReceiveReady(adapter) == NDIS_STATUS_SUCCESS) + { + if (/*!adapter->m_tun &&*/ ((irpSp->Parameters.Write.Length) >= ETHERNET_HEADER_SIZE)) + { + PNET_BUFFER_LIST netBufferList; + + DUMP_PACKET ("IRP_MJ_WRITE ETH", + (unsigned char *) Irp->AssociatedIrp.SystemBuffer, + irpSp->Parameters.Write.Length); + + //===================================================== + // If IPv4 packet, check whether or not packet + // was truncated. + //===================================================== +#if PACKET_TRUNCATION_CHECK + IPv4PacketSizeVerify ( + (unsigned char *) Irp->AssociatedIrp.SystemBuffer, + irpSp->Parameters.Write.Length, + FALSE, + "RX", + &adapter->m_RxTrunc + ); +#endif + (Irp->MdlAddress)->Next = NULL; // No next MDL + + // Allocate the NBL and NB. Link MDL chain to NB. + netBufferList = NdisAllocateNetBufferAndNetBufferList( + adapter->ReceiveNblPool, + 0, // ContextSize + 0, // ContextBackFill + Irp->MdlAddress, // MDL chain + 0, + dataLength + ); + + if(netBufferList != NULL) + { + LONG nblCount; + + NET_BUFFER_LIST_NEXT_NBL(netBufferList) = NULL; // Only one NBL + + // Stash IRP pointer in NBL MiniportReserved[0] field. + netBufferList->MiniportReserved[0] = Irp; + netBufferList->MiniportReserved[1] = NULL; + + // This IRP is pended. + IoMarkIrpPending(Irp); + + // This IRP cannot be cancelled while in-flight. + IoSetCancelRoutine(Irp,NULL); + + TAP_RX_NBL_FLAGS_CLEAR_ALL(netBufferList); + + // Increment in-flight receive NBL count. + nblCount = NdisInterlockedIncrement(&adapter->ReceiveNblInFlightCount); + ASSERT(nblCount > 0 ); + + // + // Indicate the packet + // ------------------- + // Irp->AssociatedIrp.SystemBuffer with length irpSp->Parameters.Write.Length + // contains the complete packet including Ethernet header and payload. + // + NdisMIndicateReceiveNetBufferLists( + adapter->MiniportAdapterHandle, + netBufferList, + NDIS_DEFAULT_PORT_NUMBER, + 1, // NumberOfNetBufferLists + 0 // ReceiveFlags + ); + + ntStatus = STATUS_PENDING; + } + else + { + DEBUGP (("[%s] NdisMIndicateReceiveNetBufferLists failed in IRP_MJ_WRITE\n", + MINIPORT_INSTANCE_ID (adapter))); + NOTE_ERROR (); + + // Fail the IRP + Irp->IoStatus.Information = 0; + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + } + } + /* + else if (adapter->m_tun && ((irpSp->Parameters.Write.Length) >= IP_HEADER_SIZE)) + { + PETH_HEADER p_UserToTap = &adapter->m_UserToTap; + PMDL mdl; // Head of MDL chain. + + // For IPv6, need to use Ethernet header with IPv6 proto + if ( IPH_GET_VER( ((IPHDR*) Irp->AssociatedIrp.SystemBuffer)->version_len) == 6 ) + { + p_UserToTap = &adapter->m_UserToTap_IPv6; + } + + DUMP_PACKET2 ("IRP_MJ_WRITE P2P", + p_UserToTap, + (unsigned char *) Irp->AssociatedIrp.SystemBuffer, + irpSp->Parameters.Write.Length); + + //===================================================== + // If IPv4 packet, check whether or not packet + // was truncated. + //===================================================== +#if PACKET_TRUNCATION_CHECK + IPv4PacketSizeVerify ( + (unsigned char *) Irp->AssociatedIrp.SystemBuffer, + irpSp->Parameters.Write.Length, + TRUE, + "RX", + &adapter->m_RxTrunc + ); +#endif + + // + // Allocate MDL for Ethernet header + // -------------------------------- + // Irp->AssociatedIrp.SystemBuffer with length irpSp->Parameters.Write.Length + // contains the only the Ethernet payload. Prepend the user-mode provided + // payload with the Ethernet header pointed to by p_UserToTap. + // + mdl = NdisAllocateMdl( + adapter->MiniportAdapterHandle, + p_UserToTap, + sizeof(ETH_HEADER) + ); + + if(mdl != NULL) + { + PNET_BUFFER_LIST netBufferList; + + // Chain user's Ethernet payload behind Ethernet header. + mdl->Next = Irp->MdlAddress; + (Irp->MdlAddress)->Next = NULL; // No next MDL + + // Allocate the NBL and NB. Link MDL chain to NB. + netBufferList = NdisAllocateNetBufferAndNetBufferList( + adapter->ReceiveNblPool, + 0, // ContextSize + 0, // ContextBackFill + mdl, // MDL chain + 0, + sizeof(ETH_HEADER) + dataLength + ); + + if(netBufferList != NULL) + { + LONG nblCount; + + NET_BUFFER_LIST_NEXT_NBL(netBufferList) = NULL; // Only one NBL + + // This IRP is pended. + IoMarkIrpPending(Irp); + + // This IRP cannot be cancelled while in-flight. + IoSetCancelRoutine(Irp,NULL); + + // Stash IRP pointer in NBL MiniportReserved[0] field. + netBufferList->MiniportReserved[0] = Irp; + netBufferList->MiniportReserved[1] = NULL; + + // Set flag indicating that this is P2P packet + TAP_RX_NBL_FLAGS_CLEAR_ALL(netBufferList); + TAP_RX_NBL_FLAG_SET(netBufferList,TAP_RX_NBL_FLAGS_IS_P2P); + + // Increment in-flight receive NBL count. + nblCount = NdisInterlockedIncrement(&adapter->ReceiveNblInFlightCount); + ASSERT(nblCount > 0 ); + + // + // Indicate the packet + // + NdisMIndicateReceiveNetBufferLists( + adapter->MiniportAdapterHandle, + netBufferList, + NDIS_DEFAULT_PORT_NUMBER, + 1, // NumberOfNetBufferLists + 0 // ReceiveFlags + ); + + ntStatus = STATUS_PENDING; + } + else + { + mdl->Next = NULL; + NdisFreeMdl(mdl); + + DEBUGP (("[%s] NdisMIndicateReceiveNetBufferLists failed in IRP_MJ_WRITE\n", + MINIPORT_INSTANCE_ID (adapter))); + NOTE_ERROR (); + + // Fail the IRP + Irp->IoStatus.Information = 0; + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + } + } + else + { + DEBUGP (("[%s] NdisAllocateMdl failed in IRP_MJ_WRITE\n", + MINIPORT_INSTANCE_ID (adapter))); + NOTE_ERROR (); + + // Fail the IRP + Irp->IoStatus.Information = 0; + ntStatus = STATUS_INSUFFICIENT_RESOURCES; + } + } + */ + else + { + DEBUGP (("[%s] Bad buffer size in IRP_MJ_WRITE, len=%d\n", + MINIPORT_INSTANCE_ID (adapter), + irpSp->Parameters.Write.Length)); + NOTE_ERROR (); + + Irp->IoStatus.Information = 0; // ETHERNET_HEADER_SIZE; + Irp->IoStatus.Status = ntStatus = STATUS_BUFFER_TOO_SMALL; + } + } + else + { + DEBUGP (("[%s] Lying send in IRP_MJ_WRITE while adapter paused\n", + MINIPORT_INSTANCE_ID (adapter))); + + ntStatus = STATUS_SUCCESS; + } + + if (ntStatus != STATUS_PENDING) + { + Irp->IoStatus.Status = ntStatus; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + } + + return ntStatus; +} + diff --git a/windows/TapDriver6/tap-windows.h b/windows/TapDriver6/tap-windows.h new file mode 100644 index 0000000..fd41a79 --- /dev/null +++ b/windows/TapDriver6/tap-windows.h @@ -0,0 +1,83 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __TAP_WIN_H +#define __TAP_WIN_H + +/* + * ============= + * TAP IOCTLs + * ============= + */ + +#define TAP_WIN_CONTROL_CODE(request,method) \ + CTL_CODE (FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS) + +/* Present in 8.1 */ + +#define TAP_WIN_IOCTL_GET_MAC TAP_WIN_CONTROL_CODE (1, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_GET_VERSION TAP_WIN_CONTROL_CODE (2, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_GET_MTU TAP_WIN_CONTROL_CODE (3, METHOD_BUFFERED) +//#define TAP_WIN_IOCTL_GET_INFO TAP_WIN_CONTROL_CODE (4, METHOD_BUFFERED) +//#define TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT TAP_WIN_CONTROL_CODE (5, METHOD_BUFFERED) +#define TAP_WIN_IOCTL_SET_MEDIA_STATUS TAP_WIN_CONTROL_CODE (6, METHOD_BUFFERED) +//#define TAP_WIN_IOCTL_CONFIG_DHCP_MASQ TAP_WIN_CONTROL_CODE (7, METHOD_BUFFERED) +#if DBG +#define TAP_WIN_IOCTL_GET_LOG_LINE TAP_WIN_CONTROL_CODE (8, METHOD_BUFFERED) +#endif +//#define TAP_WIN_IOCTL_CONFIG_DHCP_SET_OPT TAP_WIN_CONTROL_CODE (9, METHOD_BUFFERED) + +/* Added in 8.2 */ + +/* obsoletes TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT */ +//#define TAP_WIN_IOCTL_CONFIG_TUN TAP_WIN_CONTROL_CODE (10, METHOD_BUFFERED) + +// Used by ZT1 to get multicast memberships at the L2 level -- Windows provides no native way to do this that I know of +#define TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS TAP_WIN_CONTROL_CODE (11, METHOD_BUFFERED) +// Must be the same as NIC_MAX_MCAST_LIST in constants.h +#define TAP_MAX_MCAST_LIST 128 +// Amount of memory that must be provided to ioctl TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS +#define TAP_WIN_IOCTL_GET_MULTICAST_MEMBERSHIPS_OUTPUT_BUF_SIZE (TAP_MAX_MCAST_LIST * 6) + +/* + * ================= + * Registry keys + * ================= + */ + +#define ADAPTER_KEY "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}" + +#define NETWORK_CONNECTIONS_KEY "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}" + +/* + * ====================== + * Filesystem prefixes + * ====================== + */ + +#define USERMODEDEVICEDIR "\\\\.\\Global\\" +#define SYSDEVICEDIR "\\Device\\" +#define USERDEVICEDIR "\\DosDevices\\Global\\" +#define TAP_WIN_SUFFIX ".tap" + +#endif // __TAP_WIN_H diff --git a/windows/TapDriver6/tap.h b/windows/TapDriver6/tap.h new file mode 100644 index 0000000..079b279 --- /dev/null +++ b/windows/TapDriver6/tap.h @@ -0,0 +1,88 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __TAP_H +#define __TAP_H + +#ifndef NDIS_SUPPORT_NDIS6 +#define NDIS_SUPPORT_NDIS6 1 +#define NDIS_SUPPORT_NDIS61 1 +#define NDIS_WDM1 1 +#define NDIS61_MINIPORT 1 +#endif + +#include +#include +#include +#include + +#include "config.h" +#include "lock.h" +#include "constants.h" +#include "proto.h" +#include "mem.h" +#include "macinfo.h" +#include "error.h" +#include "endian.h" +#include "types.h" +#include "adapter.h" +#include "device.h" +#include "prototypes.h" +#include "tap-windows.h" + +//======================================================== +// Check for truncated IPv4 packets, log errors if found. +//======================================================== +#define PACKET_TRUNCATION_CHECK 0 + +//======================================================== +// EXPERIMENTAL -- Configure TAP device object to be +// accessible from non-administrative accounts, based +// on an advanced properties setting. +// +// Duplicates the functionality of OpenVPN's +// --allow-nonadmin directive. +//======================================================== +#define ENABLE_NONADMIN 1 + +// +// The driver has exactly one instance of the TAP_GLOBAL structure. NDIS keeps +// an opaque handle to this data, (it doesn't attempt to read or interpret this +// data), and it passes the handle back to the miniport in MiniportSetOptions +// and MiniportInitializeEx. +// +typedef struct _TAP_GLOBAL +{ + LIST_ENTRY AdapterList; + + NDIS_RW_LOCK Lock; + + NDIS_HANDLE NdisDriverHandle; // From NdisMRegisterMiniportDriver + +} TAP_GLOBAL, *PTAP_GLOBAL; + + +// Global data +extern TAP_GLOBAL GlobalData; + +#endif // __TAP_H diff --git a/windows/TapDriver6/tapdrvr.c b/windows/TapDriver6/tapdrvr.c new file mode 100644 index 0000000..6c537f1 --- /dev/null +++ b/windows/TapDriver6/tapdrvr.c @@ -0,0 +1,232 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +//====================================================== +// This driver is designed to work on Windows Vista or higher +// versions of Windows. +// +// It is SMP-safe and handles power management. +// +// By default we operate as a "tap" virtual ethernet +// 802.3 interface, but we can emulate a "tun" +// interface (point-to-point IPv4) through the +// TAP_WIN_IOCTL_CONFIG_POINT_TO_POINT or +// TAP_WIN_IOCTL_CONFIG_TUN ioctl. +//====================================================== + +// +// Include files. +// + +#include + +#include "tap.h" + + +// Global data +TAP_GLOBAL GlobalData; + + +#ifdef ALLOC_PRAGMA +#pragma alloc_text( INIT, DriverEntry ) +#pragma alloc_text( PAGE, TapDriverUnload) +#endif // ALLOC_PRAGMA + +NTSTATUS +DriverEntry( + __in PDRIVER_OBJECT DriverObject, + __in PUNICODE_STRING RegistryPath + ) +/*++ +Routine Description: + + In the context of its DriverEntry function, a miniport driver associates + itself with NDIS, specifies the NDIS version that it is using, and + registers its entry points. + + +Arguments: + PVOID DriverObject - pointer to the driver object. + PVOID RegistryPath - pointer to the driver registry path. + + Return Value: + + NTSTATUS code + +--*/ +{ + NTSTATUS status; + + UNREFERENCED_PARAMETER(RegistryPath); + + DEBUGP (("[TAP] --> DriverEntry; version [%d.%d] %s %s\n", + TAP_DRIVER_MAJOR_VERSION, + TAP_DRIVER_MINOR_VERSION, + __DATE__, + __TIME__)); + + DEBUGP (("[TAP] Registry Path: '%wZ'\n", RegistryPath)); + + // + // Initialize any driver-global variables here. + // + NdisZeroMemory(&GlobalData, sizeof(GlobalData)); + + // + // The ApaterList in the GlobalData structure is used to track multiple + // adapters controlled by this miniport. + // + NdisInitializeListHead(&GlobalData.AdapterList); + + // + // This lock protects the AdapterList. + // + NdisInitializeReadWriteLock(&GlobalData.Lock); + + do + { + NDIS_MINIPORT_DRIVER_CHARACTERISTICS miniportCharacteristics; + + NdisZeroMemory(&miniportCharacteristics, sizeof(miniportCharacteristics)); + + {C_ASSERT(sizeof(miniportCharacteristics) >= NDIS_SIZEOF_MINIPORT_DRIVER_CHARACTERISTICS_REVISION_2);} + miniportCharacteristics.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_DRIVER_CHARACTERISTICS; + miniportCharacteristics.Header.Size = NDIS_SIZEOF_MINIPORT_DRIVER_CHARACTERISTICS_REVISION_2; + miniportCharacteristics.Header.Revision = NDIS_MINIPORT_DRIVER_CHARACTERISTICS_REVISION_2; + + miniportCharacteristics.MajorNdisVersion = TAP_NDIS_MAJOR_VERSION; + miniportCharacteristics.MinorNdisVersion = TAP_NDIS_MINOR_VERSION; + + miniportCharacteristics.MajorDriverVersion = TAP_DRIVER_MAJOR_VERSION; + miniportCharacteristics.MinorDriverVersion = TAP_DRIVER_MINOR_VERSION; + + miniportCharacteristics.Flags = 0; + + //miniportCharacteristics.SetOptionsHandler = MPSetOptions; // Optional + miniportCharacteristics.InitializeHandlerEx = AdapterCreate; + miniportCharacteristics.HaltHandlerEx = AdapterHalt; + miniportCharacteristics.UnloadHandler = TapDriverUnload; + miniportCharacteristics.PauseHandler = AdapterPause; + miniportCharacteristics.RestartHandler = AdapterRestart; + miniportCharacteristics.OidRequestHandler = AdapterOidRequest; + miniportCharacteristics.SendNetBufferListsHandler = AdapterSendNetBufferLists; + miniportCharacteristics.ReturnNetBufferListsHandler = AdapterReturnNetBufferLists; + miniportCharacteristics.CancelSendHandler = AdapterCancelSend; + miniportCharacteristics.CheckForHangHandlerEx = AdapterCheckForHangEx; + miniportCharacteristics.ResetHandlerEx = AdapterReset; + miniportCharacteristics.DevicePnPEventNotifyHandler = AdapterDevicePnpEventNotify; + miniportCharacteristics.ShutdownHandlerEx = AdapterShutdownEx; + miniportCharacteristics.CancelOidRequestHandler = AdapterCancelOidRequest; + + // + // Associate the miniport driver with NDIS by calling the + // NdisMRegisterMiniportDriver. This function returns an NdisDriverHandle. + // The miniport driver must retain this handle but it should never attempt + // to access or interpret this handle. + // + // By calling NdisMRegisterMiniportDriver, the driver indicates that it + // is ready for NDIS to call the driver's MiniportSetOptions and + // MiniportInitializeEx handlers. + // + DEBUGP (("[TAP] Calling NdisMRegisterMiniportDriver...\n")); + //NDIS_DECLARE_MINIPORT_DRIVER_CONTEXT(TAP_GLOBAL); + status = NdisMRegisterMiniportDriver( + DriverObject, + RegistryPath, + &GlobalData, + &miniportCharacteristics, + &GlobalData.NdisDriverHandle + ); + + if (NDIS_STATUS_SUCCESS == status) + { + DEBUGP (("[TAP] Registered miniport successfully\n")); + } + else + { + DEBUGP(("[TAP] NdisMRegisterMiniportDriver failed: %8.8X\n", status)); + TapDriverUnload(DriverObject); + status = NDIS_STATUS_FAILURE; + break; + } + } while(FALSE); + + DEBUGP (("[TAP] <-- DriverEntry; status = %8.8X\n",status)); + + return status; +} + +VOID +TapDriverUnload( + __in PDRIVER_OBJECT DriverObject + ) +/*++ + +Routine Description: + + The unload handler is called during driver unload to free up resources + acquired in DriverEntry. This handler is registered in DriverEntry through + NdisMRegisterMiniportDriver. Note that an unload handler differs from + a MiniportHalt function in that this unload handler releases resources that + are global to the driver, while the halt handler releases resource for a + particular adapter. + + Runs at IRQL = PASSIVE_LEVEL. + +Arguments: + + DriverObject Not used + +Return Value: + + None. + +--*/ +{ + PDEVICE_OBJECT deviceObject = DriverObject->DeviceObject; + UNICODE_STRING uniWin32NameString; + + DEBUGP (("[TAP] --> TapDriverUnload; version [%d.%d] %s %s unloaded\n", + TAP_DRIVER_MAJOR_VERSION, + TAP_DRIVER_MINOR_VERSION, + __DATE__, + __TIME__ + )); + + PAGED_CODE(); + + // + // Clean up all globals that were allocated in DriverEntry + // + + ASSERT(IsListEmpty(&GlobalData.AdapterList)); + + if(GlobalData.NdisDriverHandle != NULL ) + { + NdisMDeregisterMiniportDriver(GlobalData.NdisDriverHandle); + } + + DEBUGP (("[TAP] <-- TapDriverUnload\n")); +} + diff --git a/windows/TapDriver6/txpath.c b/windows/TapDriver6/txpath.c new file mode 100644 index 0000000..7993ca4 --- /dev/null +++ b/windows/TapDriver6/txpath.c @@ -0,0 +1,1175 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// +// Include files. +// + +#include "tap.h" + +//====================================================================== +// TAP Send Path Support +//====================================================================== + +#ifdef ALLOC_PRAGMA +#pragma alloc_text( PAGE, TapDeviceRead) +#endif // ALLOC_PRAGMA + +// checksum code for ICMPv6 packet, taken from dhcp.c / udp_checksum +// see RFC 4443, 2.3, and RFC 2460, 8.1 +USHORT +icmpv6_checksum( + __in const UCHAR *buf, + __in const int len_icmpv6, + __in const UCHAR *saddr6, + __in const UCHAR *daddr6 + ) +{ + USHORT word16; + ULONG sum = 0; + int i; + + // make 16 bit words out of every two adjacent 8 bit words and + // calculate the sum of all 16 bit words + for (i = 0; i < len_icmpv6; i += 2) + { + word16 = ((buf[i] << 8) & 0xFF00) + ((i + 1 < len_icmpv6) ? (buf[i+1] & 0xFF) : 0); + sum += word16; + } + + // add the IPv6 pseudo header which contains the IP source and destination addresses + for (i = 0; i < 16; i += 2) + { + word16 =((saddr6[i] << 8) & 0xFF00) + (saddr6[i+1] & 0xFF); + sum += word16; + } + + for (i = 0; i < 16; i += 2) + { + word16 =((daddr6[i] << 8) & 0xFF00) + (daddr6[i+1] & 0xFF); + sum += word16; + } + + // the next-header number and the length of the ICMPv6 packet + sum += (USHORT) IPPROTO_ICMPV6 + (USHORT) len_icmpv6; + + // keep only the last 16 bits of the 32 bit calculated sum and add the carries + while (sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + + // Take the one's complement of sum + return ((USHORT) ~sum); +} + +/* + +// check IPv6 packet for "is this an IPv6 Neighbor Solicitation that +// the tap driver needs to answer?" +// see RFC 4861 4.3 for the different cases +static IPV6ADDR IPV6_NS_TARGET_MCAST = + { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 0x08 }; +static IPV6ADDR IPV6_NS_TARGET_UNICAST = + { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; + +BOOLEAN +HandleIPv6NeighborDiscovery( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in UCHAR * m_Data + ) +{ + const ETH_HEADER * e = (ETH_HEADER *) m_Data; + const IPV6HDR *ipv6 = (IPV6HDR *) (m_Data + sizeof (ETH_HEADER)); + const ICMPV6_NS * icmpv6_ns = (ICMPV6_NS *) (m_Data + sizeof (ETH_HEADER) + sizeof (IPV6HDR)); + ICMPV6_NA_PKT *na; + USHORT icmpv6_len, icmpv6_csum; + + // we don't really care about the destination MAC address here + // - it's either a multicast MAC, or the userland destination MAC + // but since the TAP driver is point-to-point, all packets are "for us" + + // IPv6 target address must be ff02::1::ff00:8 (multicast for + // initial NS) or fe80::1 (unicast for recurrent NUD) + if ( memcmp( ipv6->daddr, IPV6_NS_TARGET_MCAST, + sizeof(IPV6ADDR) ) != 0 && + memcmp( ipv6->daddr, IPV6_NS_TARGET_UNICAST, + sizeof(IPV6ADDR) ) != 0 ) + { + return FALSE; // wrong target address + } + + // IPv6 Next-Header must be ICMPv6 + if ( ipv6->nexthdr != IPPROTO_ICMPV6 ) + { + return FALSE; // wrong next-header + } + + // ICMPv6 type+code must be 135/0 for NS + if ( icmpv6_ns->type != ICMPV6_TYPE_NS || + icmpv6_ns->code != ICMPV6_CODE_0 ) + { + return FALSE; // wrong ICMPv6 type + } + + // ICMPv6 target address must be fe80::8 (magic) + if ( memcmp( icmpv6_ns->target_addr, IPV6_NS_TARGET_UNICAST, + sizeof(IPV6ADDR) ) != 0 ) + { + return FALSE; // not for us + } + + // packet identified, build magic response packet + + na = (ICMPV6_NA_PKT *) MemAlloc (sizeof (ICMPV6_NA_PKT), TRUE); + if ( !na ) return FALSE; + + //------------------------------------------------ + // Initialize Neighbour Advertisement reply packet + //------------------------------------------------ + + // ethernet header + na->eth.proto = htons(NDIS_ETH_TYPE_IPV6); + ETH_COPY_NETWORK_ADDRESS(na->eth.dest, Adapter->PermanentAddress); + ETH_COPY_NETWORK_ADDRESS(na->eth.src, Adapter->m_TapToUser.dest); + + // IPv6 header + na->ipv6.version_prio = ipv6->version_prio; + NdisMoveMemory( na->ipv6.flow_lbl, ipv6->flow_lbl, + sizeof(na->ipv6.flow_lbl) ); + icmpv6_len = sizeof(ICMPV6_NA_PKT) - sizeof(ETH_HEADER) - sizeof(IPV6HDR); + na->ipv6.payload_len = htons(icmpv6_len); + na->ipv6.nexthdr = IPPROTO_ICMPV6; + na->ipv6.hop_limit = 255; + NdisMoveMemory( na->ipv6.saddr, IPV6_NS_TARGET_UNICAST, + sizeof(IPV6ADDR) ); + NdisMoveMemory( na->ipv6.daddr, ipv6->saddr, + sizeof(IPV6ADDR) ); + + // ICMPv6 + na->icmpv6.type = ICMPV6_TYPE_NA; + na->icmpv6.code = ICMPV6_CODE_0; + na->icmpv6.checksum = 0; + na->icmpv6.rso_bits = 0x60; // Solicited + Override + NdisZeroMemory( na->icmpv6.reserved, sizeof(na->icmpv6.reserved) ); + NdisMoveMemory( na->icmpv6.target_addr, IPV6_NS_TARGET_UNICAST, + sizeof(IPV6ADDR) ); + + // ICMPv6 option "Target Link Layer Address" + na->icmpv6.opt_type = ICMPV6_OPTION_TLLA; + na->icmpv6.opt_length = ICMPV6_LENGTH_TLLA; + ETH_COPY_NETWORK_ADDRESS( na->icmpv6.target_macaddr, Adapter->m_TapToUser.dest ); + + // calculate and set checksum + icmpv6_csum = icmpv6_checksum ( + (UCHAR*) &(na->icmpv6), + icmpv6_len, + na->ipv6.saddr, + na->ipv6.daddr + ); + + na->icmpv6.checksum = htons( icmpv6_csum ); + + DUMP_PACKET ("HandleIPv6NeighborDiscovery", + (unsigned char *) na, + sizeof (ICMPV6_NA_PKT)); + + IndicateReceivePacket (Adapter, (UCHAR *) na, sizeof (ICMPV6_NA_PKT)); + + MemFree (na, sizeof (ICMPV6_NA_PKT)); + + return TRUE; // all fine +} + +//=================================================== +// Generate an ARP reply message for specific kinds +// ARP queries. +//=================================================== +BOOLEAN +ProcessARP( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in const PARP_PACKET src, + __in const IPADDR adapter_ip, + __in const IPADDR ip_network, + __in const IPADDR ip_netmask, + __in const MACADDR mac + ) +{ + //----------------------------------------------- + // Is this the kind of packet we are looking for? + //----------------------------------------------- + if (src->m_Proto == htons (NDIS_ETH_TYPE_ARP) + && MAC_EQUAL (src->m_MAC_Source, Adapter->PermanentAddress) + && MAC_EQUAL (src->m_ARP_MAC_Source, Adapter->PermanentAddress) + && ETH_IS_BROADCAST(src->m_MAC_Destination) + && src->m_ARP_Operation == htons (ARP_REQUEST) + && src->m_MAC_AddressType == htons (MAC_ADDR_TYPE) + && src->m_MAC_AddressSize == sizeof (MACADDR) + && src->m_PROTO_AddressType == htons (NDIS_ETH_TYPE_IPV4) + && src->m_PROTO_AddressSize == sizeof (IPADDR) + && src->m_ARP_IP_Source == adapter_ip + && (src->m_ARP_IP_Destination & ip_netmask) == ip_network + && src->m_ARP_IP_Destination != adapter_ip) + { + ARP_PACKET *arp = (ARP_PACKET *) MemAlloc (sizeof (ARP_PACKET), TRUE); + if (arp) + { + //---------------------------------------------- + // Initialize ARP reply fields + //---------------------------------------------- + arp->m_Proto = htons (NDIS_ETH_TYPE_ARP); + arp->m_MAC_AddressType = htons (MAC_ADDR_TYPE); + arp->m_PROTO_AddressType = htons (NDIS_ETH_TYPE_IPV4); + arp->m_MAC_AddressSize = sizeof (MACADDR); + arp->m_PROTO_AddressSize = sizeof (IPADDR); + arp->m_ARP_Operation = htons (ARP_REPLY); + + //---------------------------------------------- + // ARP addresses + //---------------------------------------------- + ETH_COPY_NETWORK_ADDRESS (arp->m_MAC_Source, mac); + ETH_COPY_NETWORK_ADDRESS (arp->m_MAC_Destination, Adapter->PermanentAddress); + ETH_COPY_NETWORK_ADDRESS (arp->m_ARP_MAC_Source, mac); + ETH_COPY_NETWORK_ADDRESS (arp->m_ARP_MAC_Destination, Adapter->PermanentAddress); + arp->m_ARP_IP_Source = src->m_ARP_IP_Destination; + arp->m_ARP_IP_Destination = adapter_ip; + + DUMP_PACKET ("ProcessARP", + (unsigned char *) arp, + sizeof (ARP_PACKET)); + + IndicateReceivePacket (Adapter, (UCHAR *) arp, sizeof (ARP_PACKET)); + + MemFree (arp, sizeof (ARP_PACKET)); + } + + return TRUE; + } + else + return FALSE; +} +*/ + +//============================================================= +// CompleteIRP is normally called with an adapter -> userspace +// network packet and an IRP (Pending I/O request) from userspace. +// +// The IRP will normally represent a queued overlapped read +// operation from userspace that is in a wait state. +// +// Use the ethernet packet to satisfy the IRP. +//============================================================= + +VOID +tapCompletePendingReadIrp( + __in PIRP Irp, + __in PTAP_PACKET TapPacket + ) +{ + int offset; + int len; + NTSTATUS status = STATUS_UNSUCCESSFUL; + + ASSERT(Irp); + ASSERT(TapPacket); + + //------------------------------------------- + // While TapPacket always contains a + // full ethernet packet, including the + // ethernet header, in point-to-point mode, + // we only want to return the IPv4 + // component. + //------------------------------------------- + + if (TapPacket->m_SizeFlags & TP_TUN) + { + offset = ETHERNET_HEADER_SIZE; + len = (int) (TapPacket->m_SizeFlags & TP_SIZE_MASK) - ETHERNET_HEADER_SIZE; + } + else + { + offset = 0; + len = (TapPacket->m_SizeFlags & TP_SIZE_MASK); + } + + if (len < 0 || (int) Irp->IoStatus.Information < len) + { + Irp->IoStatus.Information = 0; + Irp->IoStatus.Status = status = STATUS_BUFFER_OVERFLOW; + NOTE_ERROR (); + } + else + { + Irp->IoStatus.Information = len; + Irp->IoStatus.Status = status = STATUS_SUCCESS; + + // Copy packet data + NdisMoveMemory( + Irp->AssociatedIrp.SystemBuffer, + TapPacket->m_Data + offset, + len + ); + } + + // Free the TAP packet + NdisFreeMemory(TapPacket,0,0); + + // Complete the IRP + IoCompleteRequest (Irp, IO_NETWORK_INCREMENT); +} + +VOID +tapProcessSendPacketQueue( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + KIRQL irql; + + // Process the send packet queue + KeAcquireSpinLock(&Adapter->SendPacketQueue.QueueLock,&irql); + + while(Adapter->SendPacketQueue.Count > 0 ) + { + PIRP irp; + PTAP_PACKET tapPacket; + + // Fetch a read IRP + irp = IoCsqRemoveNextIrp( + &Adapter->PendingReadIrpQueue.CsqQueue, + NULL + ); + + if( irp == NULL ) + { + // No IRP to satisfy + break; + } + + // Fetch a queued TAP send packet + tapPacket = tapPacketRemoveHeadLocked( + &Adapter->SendPacketQueue + ); + + ASSERT(tapPacket); + + // BUGBUG!!! Investigate whether release/reacquire can cause + // out-of-order IRP completion. Also, whether user-mode can + // tolerate out-of-order packets. + + // Release packet queue lock while completing the IRP + //KeReleaseSpinLock(&Adapter->SendPacketQueue.QueueLock,irql); + + // Complete the read IRP from queued TAP send packet. + tapCompletePendingReadIrp(irp,tapPacket); + + // Reqcquire packet queue lock after completing the IRP + //KeAcquireSpinLock(&Adapter->SendPacketQueue.QueueLock,&irql); + } + + KeReleaseSpinLock(&Adapter->SendPacketQueue.QueueLock,irql); +} + +// Flush the pending send TAP packet queue. +VOID +tapFlushSendPacketQueue( + __in PTAP_ADAPTER_CONTEXT Adapter + ) +{ + KIRQL irql; + + // Process the send packet queue + KeAcquireSpinLock(&Adapter->SendPacketQueue.QueueLock,&irql); + + DEBUGP (("[TAP] tapFlushSendPacketQueue: Flushing %d TAP packets\n", + Adapter->SendPacketQueue.Count)); + + while(Adapter->SendPacketQueue.Count > 0 ) + { + PTAP_PACKET tapPacket; + + // Fetch a queued TAP send packet + tapPacket = tapPacketRemoveHeadLocked( + &Adapter->SendPacketQueue + ); + + ASSERT(tapPacket); + + // Free the TAP packet + NdisFreeMemory(tapPacket,0,0); + } + + KeReleaseSpinLock(&Adapter->SendPacketQueue.QueueLock,irql); +} + +VOID +tapAdapterTransmit( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in PNET_BUFFER NetBuffer, + __in BOOLEAN DispatchLevel + ) +/*++ + +Routine Description: + + This routine is called to transmit an individual net buffer using a + style similar to the previous NDIS 5 AdapterTransmit function. + + In this implementation adapter state and NB length checks have already + been done before this function has been called. + + The net buffer will be completed by the calling routine after this + routine exits. So, under this design it is necessary to make a deep + copy of frame data in the net buffer. + + This routine creates a flat buffer copy of NB frame data. This is an + unnecessary performance bottleneck. However, the bottleneck is probably + not significant or measurable except for adapters running at 1Gbps or + greater speeds. Since this adapter is currently running at 100Mbps this + defect can be ignored. + + Runs at IRQL <= DISPATCH_LEVEL + +Arguments: + + Adapter Pointer to our adapter context + NetBuffer Pointer to the net buffer to transmit + DispatchLevel TRUE if called at IRQL == DISPATCH_LEVEL + +Return Value: + + None. + + In the Microsoft NDIS 6 architecture there is no per-packet status. + +--*/ +{ + NDIS_STATUS status; + ULONG packetLength; + PTAP_PACKET tapPacket; + PVOID packetData; + + packetLength = NET_BUFFER_DATA_LENGTH(NetBuffer); + + // Allocate TAP packet memory + tapPacket = (PTAP_PACKET )NdisAllocateMemoryWithTagPriority( + Adapter->MiniportAdapterHandle, + TAP_PACKET_SIZE (packetLength), + TAP_PACKET_TAG, + NormalPoolPriority + ); + + if(tapPacket == NULL) + { + DEBUGP (("[TAP] tapAdapterTransmit: TAP packet allocation failed\n")); + return; + } + + tapPacket->m_SizeFlags = (packetLength & TP_SIZE_MASK); + + // + // Reassemble packet contents + // -------------------------- + // NdisGetDataBuffer does most of the work. There are two cases: + // + // 1.) If the NB data was not contiguous it will copy the entire + // NB's data to m_data and return pointer to m_data. + // 2.) If the NB data was contiguous it returns a pointer to the + // first byte of the contiguous data instead of a pointer to m_Data. + // In this case the data will not have been copied to m_Data. Copy + // to m_Data will need to be done in an extra step. + // + // Case 1.) is the most likely in normal operation. + // + packetData = NdisGetDataBuffer(NetBuffer,packetLength,tapPacket->m_Data,1,0); + + if(packetData == NULL) + { + DEBUGP (("[TAP] tapAdapterTransmit: Could not get packet data\n")); + + NdisFreeMemory(tapPacket,0,0); + + return; + } + + if(packetData != tapPacket->m_Data) + { + // Packet data was contiguous and not yet copied to m_Data. + NdisMoveMemory(tapPacket->m_Data,packetData,packetLength); + } + + DUMP_PACKET ("AdapterTransmit", tapPacket->m_Data, packetLength); + + //===================================================== + // If IPv4 packet, check whether or not packet + // was truncated. + //===================================================== +#if PACKET_TRUNCATION_CHECK + IPv4PacketSizeVerify( + tapPacket->m_Data, + packetLength, + FALSE, + "TX", + &Adapter->m_TxTrunc + ); +#endif + + //===================================================== + // Are we running in DHCP server masquerade mode? + // + // If so, catch both DHCP requests and ARP queries + // to resolve the address of our virtual DHCP server. + //===================================================== +#if 0 + if (Adapter->m_dhcp_enabled) + { + const ETH_HEADER *eth = (ETH_HEADER *) tapPacket->m_Data; + const IPHDR *ip = (IPHDR *) (tapPacket->m_Data + sizeof (ETH_HEADER)); + const UDPHDR *udp = (UDPHDR *) (tapPacket->m_Data + sizeof (ETH_HEADER) + sizeof (IPHDR)); + + // ARP packet? + if (packetLength == sizeof (ARP_PACKET) + && eth->proto == htons (NDIS_ETH_TYPE_ARP) + && Adapter->m_dhcp_server_arp + ) + { + if (ProcessARP( + Adapter, + (PARP_PACKET) tapPacket->m_Data, + Adapter->m_dhcp_addr, + Adapter->m_dhcp_server_ip, + ~0, + Adapter->m_dhcp_server_mac) + ) + { + goto no_queue; + } + } + + // DHCP packet? + else if (packetLength >= sizeof (ETH_HEADER) + sizeof (IPHDR) + sizeof (UDPHDR) + sizeof (DHCP) + && eth->proto == htons (NDIS_ETH_TYPE_IPV4) + && ip->version_len == 0x45 // IPv4, 20 byte header + && ip->protocol == IPPROTO_UDP + && udp->dest == htons (BOOTPS_PORT) + ) + { + const DHCP *dhcp = (DHCP *) (tapPacket->m_Data + + sizeof (ETH_HEADER) + + sizeof (IPHDR) + + sizeof (UDPHDR)); + + const int optlen = packetLength + - sizeof (ETH_HEADER) + - sizeof (IPHDR) + - sizeof (UDPHDR) + - sizeof (DHCP); + + if (optlen > 0) // we must have at least one DHCP option + { + if (ProcessDHCP (Adapter, eth, ip, udp, dhcp, optlen)) + { + goto no_queue; + } + } + else + { + goto no_queue; + } + } + } +#endif + + //=============================================== + // In Point-To-Point mode, check to see whether + // packet is ARP (handled) or IPv4 (sent to app). + // IPv6 packets are inspected for neighbour discovery + // (to be handled locally), and the rest is forwarded + // all other protocols are dropped + //=============================================== +#if 0 + if (Adapter->m_tun) + { + ETH_HEADER *e; + + e = (ETH_HEADER *) tapPacket->m_Data; + + switch (ntohs (e->proto)) + { + case NDIS_ETH_TYPE_ARP: + + // Make sure that packet is the right size for ARP. + if (packetLength != sizeof (ARP_PACKET)) + { + goto no_queue; + } + + ProcessARP ( + Adapter, + (PARP_PACKET) tapPacket->m_Data, + Adapter->m_localIP, + Adapter->m_remoteNetwork, + Adapter->m_remoteNetmask, + Adapter->m_TapToUser.dest + ); + + default: + goto no_queue; + + case NDIS_ETH_TYPE_IPV4: + + // Make sure that packet is large enough to be IPv4. + if (packetLength < (ETHERNET_HEADER_SIZE + IP_HEADER_SIZE)) + { + goto no_queue; + } + + // Only accept directed packets, not broadcasts. + if (memcmp (e, &Adapter->m_TapToUser, ETHERNET_HEADER_SIZE)) + { + goto no_queue; + } + + // Packet looks like IPv4, queue it. :-) + tapPacket->m_SizeFlags |= TP_TUN; + break; + + case NDIS_ETH_TYPE_IPV6: + // Make sure that packet is large enough to be IPv6. + if (packetLength < (ETHERNET_HEADER_SIZE + IPV6_HEADER_SIZE)) + { + goto no_queue; + } + + // Broadcasts and multicasts are handled specially + // (to be implemented) + + // Neighbor discovery packets to fe80::8 are special + // OpenVPN sets this next-hop to signal "handled by tapdrv" + if ( HandleIPv6NeighborDiscovery(Adapter,tapPacket->m_Data) ) + { + goto no_queue; + } + + // Packet looks like IPv6, queue it. :-) + tapPacket->m_SizeFlags |= TP_TUN; + } + } +#endif + + //=============================================== + // Push packet onto queue to wait for read from + // userspace. + //=============================================== + if(tapAdapterReadAndWriteReady(Adapter)) + { + tapPacketQueueInsertTail(&Adapter->SendPacketQueue,tapPacket); + } + else + { + // + // Tragedy. All this work and the packet is of no use... + // + NdisFreeMemory(tapPacket,0,0); + } + + // Return after queuing or freeing TAP packet. + return; + + // Free TAP packet without queuing. +no_queue: + if(tapPacket != NULL ) + { + NdisFreeMemory(tapPacket,0,0); + } + +exit_success: + return; +} + +VOID +tapSendNetBufferListsComplete( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in PNET_BUFFER_LIST NetBufferLists, + __in NDIS_STATUS SendCompletionStatus, + __in BOOLEAN DispatchLevel + ) +{ + PNET_BUFFER_LIST currentNbl; + PNET_BUFFER_LIST nextNbl = NULL; + ULONG sendCompleteFlags = 0; + + for ( + currentNbl = NetBufferLists; + currentNbl != NULL; + currentNbl = nextNbl + ) + { + ULONG frameType; + ULONG netBufferCount; + ULONG byteCount; + + nextNbl = NET_BUFFER_LIST_NEXT_NBL(currentNbl); + + // Set NBL completion status. + NET_BUFFER_LIST_STATUS(currentNbl) = SendCompletionStatus; + + // Fetch first NBs frame type. All linked NBs will have same type. + frameType = tapGetNetBufferFrameType(NET_BUFFER_LIST_FIRST_NB(currentNbl)); + + // Fetch statistics for all NBs linked to the NB. + netBufferCount = tapGetNetBufferCountsFromNetBufferList( + currentNbl, + &byteCount + ); + + // Update statistics by frame type + if(SendCompletionStatus == NDIS_STATUS_SUCCESS) + { + switch(frameType) + { + case NDIS_PACKET_TYPE_DIRECTED: + Adapter->FramesTxDirected += netBufferCount; + Adapter->BytesTxDirected += byteCount; + break; + + case NDIS_PACKET_TYPE_BROADCAST: + Adapter->FramesTxBroadcast += netBufferCount; + Adapter->BytesTxBroadcast += byteCount; + break; + + case NDIS_PACKET_TYPE_MULTICAST: + Adapter->FramesTxMulticast += netBufferCount; + Adapter->BytesTxMulticast += byteCount; + break; + + default: + ASSERT(FALSE); + break; + } + } + else + { + // Transmit error. + Adapter->TransmitFailuresOther += netBufferCount; + } + + currentNbl = nextNbl; + } + + if(DispatchLevel) + { + sendCompleteFlags |= NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL; + } + + // Complete the NBLs + NdisMSendNetBufferListsComplete( + Adapter->MiniportAdapterHandle, + NetBufferLists, + sendCompleteFlags + ); +} + +BOOLEAN +tapNetBufferListNetBufferLengthsValid( + __in PTAP_ADAPTER_CONTEXT Adapter, + __in PNET_BUFFER_LIST NetBufferLists + ) +/*++ + +Routine Description: + + Scan all NBLs and their linked NBs for valid lengths. + + Fairly absurd to find and packets with bogus lengths, but wise + to check anyway. If ANY packet has a bogus length, then abort the + entire send. + + The only time that one might see this check fail might be during + HCK driver testing. The HKC test might send oversize packets to + determine if the miniport can gracefully deal with them. + + This check is fairly fast. Unlike NDIS 5 packets, fetching NDIS 6 + packets lengths do not require any computation. + +Arguments: + + Adapter Pointer to our adapter context + NetBufferLists Head of a list of NBLs to examine + +Return Value: + + Returns TRUE if all NBs have reasonable lengths. + Otherwise, returns FALSE. + +--*/ +{ + PNET_BUFFER_LIST currentNbl; + + currentNbl = NetBufferLists; + + while (currentNbl) + { + PNET_BUFFER_LIST nextNbl; + PNET_BUFFER currentNb; + + // Locate next NBL + nextNbl = NET_BUFFER_LIST_NEXT_NBL(currentNbl); + + // Locate first NB (aka "packet") + currentNb = NET_BUFFER_LIST_FIRST_NB(currentNbl); + + // + // Process all NBs linked to this NBL + // + while(currentNb) + { + PNET_BUFFER nextNb; + ULONG packetLength; + + // Locate next NB + nextNb = NET_BUFFER_NEXT_NB(currentNb); + + packetLength = NET_BUFFER_DATA_LENGTH(currentNb); + + // Minimum packet size is size of Ethernet plus IPv4 headers. + ASSERT(packetLength >= (ETHERNET_HEADER_SIZE + IP_HEADER_SIZE)); + + if(packetLength < (ETHERNET_HEADER_SIZE + IP_HEADER_SIZE)) + { + return FALSE; + } + + // Maximum size should be Ethernet header size plus MTU plus modest pad for + // VLAN tag. + ASSERT( packetLength <= (ETHERNET_HEADER_SIZE + VLAN_TAG_SIZE + Adapter->MtuSize)); + + if(packetLength > (ETHERNET_HEADER_SIZE + VLAN_TAG_SIZE + Adapter->MtuSize)) + { + return FALSE; + } + + // Move to next NB + currentNb = nextNb; + } + + // Move to next NBL + currentNbl = nextNbl; + } + + return TRUE; +} + +VOID +AdapterSendNetBufferLists( + __in NDIS_HANDLE MiniportAdapterContext, + __in PNET_BUFFER_LIST NetBufferLists, + __in NDIS_PORT_NUMBER PortNumber, + __in ULONG SendFlags + ) +/*++ + +Routine Description: + + Send Packet Array handler. Called by NDIS whenever a protocol + bound to our miniport sends one or more packets. + + The input packet descriptor pointers have been ordered according + to the order in which the packets should be sent over the network + by the protocol driver that set up the packet array. The NDIS + library preserves the protocol-determined ordering when it submits + each packet array to MiniportSendPackets + + As a deserialized driver, we are responsible for holding incoming send + packets in our internal queue until they can be transmitted over the + network and for preserving the protocol-determined ordering of packet + descriptors incoming to its MiniportSendPackets function. + A deserialized miniport driver must complete each incoming send packet + with NdisMSendComplete, and it cannot call NdisMSendResourcesAvailable. + + Runs at IRQL <= DISPATCH_LEVEL + +Arguments: + + MiniportAdapterContext Pointer to our adapter + NetBufferLists Head of a list of NBLs to send + PortNumber A miniport adapter port. Default is 0. + SendFlags Additional flags for the send operation + +Return Value: + + None. Write status directly into each NBL with the NET_BUFFER_LIST_STATUS + macro. + +--*/ +{ + NDIS_STATUS status; + PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; + BOOLEAN DispatchLevel = (SendFlags & NDIS_SEND_FLAGS_DISPATCH_LEVEL); + PNET_BUFFER_LIST currentNbl; + BOOLEAN validNbLengths; + + UNREFERENCED_PARAMETER(NetBufferLists); + UNREFERENCED_PARAMETER(PortNumber); + UNREFERENCED_PARAMETER(SendFlags); + + ASSERT(PortNumber == 0); // Only the default port is supported + + // + // Can't process sends if TAP device is not open. + // ---------------------------------------------- + // Just perform a "lying send" and return packets as if they + // were successfully sent. + // + if(adapter->TapFileObject == NULL) + { + // + // Complete all NBLs and return if adapter not ready. + // + tapSendNetBufferListsComplete( + adapter, + NetBufferLists, + NDIS_STATUS_SUCCESS, + DispatchLevel + ); + + return; + } + + // + // Check Adapter send/receive ready state. + // + status = tapAdapterSendAndReceiveReady(adapter); + + if(status != NDIS_STATUS_SUCCESS) + { + // + // Complete all NBLs and return if adapter not ready. + // + tapSendNetBufferListsComplete( + adapter, + NetBufferLists, + status, + DispatchLevel + ); + + return; + } + + // + // Scan all NBLs and linked packets for valid lengths. + // --------------------------------------------------- + // If _ANY_ NB length is invalid, then fail the entire send operation. + // + // BUGBUG!!! Perhaps this should be less agressive. Fail only individual + // NBLs... + // + // If length check is valid, then TAP_PACKETS can be safely allocated + // and processed for all NBs being sent. + // + validNbLengths = tapNetBufferListNetBufferLengthsValid( + adapter, + NetBufferLists + ); + + if(!validNbLengths) + { + // + // Complete all NBLs and return if and NB length is invalid. + // + tapSendNetBufferListsComplete( + adapter, + NetBufferLists, + NDIS_STATUS_INVALID_LENGTH, + DispatchLevel + ); + + return; + } + + // + // Process each NBL individually + // + currentNbl = NetBufferLists; + + while (currentNbl) + { + PNET_BUFFER_LIST nextNbl; + PNET_BUFFER currentNb; + + // Locate next NBL + nextNbl = NET_BUFFER_LIST_NEXT_NBL(currentNbl); + + // Locate first NB (aka "packet") + currentNb = NET_BUFFER_LIST_FIRST_NB(currentNbl); + + // Transmit all NBs linked to this NBL + while(currentNb) + { + PNET_BUFFER nextNb; + + // Locate next NB + nextNb = NET_BUFFER_NEXT_NB(currentNb); + + // Transmit the NB + tapAdapterTransmit(adapter,currentNb,DispatchLevel); + + // Move to next NB + currentNb = nextNb; + } + + // Move to next NBL + currentNbl = nextNbl; + } + + // Complete all NBLs + tapSendNetBufferListsComplete( + adapter, + NetBufferLists, + NDIS_STATUS_SUCCESS, + DispatchLevel + ); + + // Attempt to complete pending read IRPs from pending TAP + // send packet queue. + tapProcessSendPacketQueue(adapter); +} + +VOID +AdapterCancelSend( + __in NDIS_HANDLE MiniportAdapterContext, + __in PVOID CancelId + ) +{ + PTAP_ADAPTER_CONTEXT adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext; + + // + // This miniport completes its sends quickly, so it isn't strictly + // neccessary to implement MiniportCancelSend. + // + // If we did implement it, we'd have to walk the Adapter->SendWaitList + // and look for any NB that points to a NBL where the CancelId matches + // NDIS_GET_NET_BUFFER_LIST_CANCEL_ID(Nbl). For any NB that so matches, + // we'd remove the NB from the SendWaitList and set the NBL's status to + // NDIS_STATUS_SEND_ABORTED, then complete the NBL. + // +} + +// IRP_MJ_READ callback. +NTSTATUS +TapDeviceRead( + PDEVICE_OBJECT DeviceObject, + PIRP Irp + ) +{ + NTSTATUS ntStatus = STATUS_SUCCESS;// Assume success + PIO_STACK_LOCATION irpSp;// Pointer to current stack location + PTAP_ADAPTER_CONTEXT adapter = NULL; + + PAGED_CODE(); + + irpSp = IoGetCurrentIrpStackLocation( Irp ); + + // + // Fetch adapter context for this device. + // -------------------------------------- + // Adapter pointer was stashed in FsContext when handle was opened. + // + adapter = (PTAP_ADAPTER_CONTEXT )(irpSp->FileObject)->FsContext; + + ASSERT(adapter); + + // + // Sanity checks on state variables + // + if (!tapAdapterReadAndWriteReady(adapter)) + { + //DEBUGP (("[%s] Interface is down in IRP_MJ_READ\n", + // MINIPORT_INSTANCE_ID (adapter))); + //NOTE_ERROR(); + + Irp->IoStatus.Status = ntStatus = STATUS_CANCELLED; + Irp->IoStatus.Information = 0; + IoCompleteRequest (Irp, IO_NO_INCREMENT); + + return ntStatus; + } + + // Save IRP-accessible copy of buffer length + Irp->IoStatus.Information = irpSp->Parameters.Read.Length; + + if (Irp->MdlAddress == NULL) + { + DEBUGP (("[%s] MdlAddress is NULL for IRP_MJ_READ\n", + MINIPORT_INSTANCE_ID (adapter))); + + NOTE_ERROR(); + Irp->IoStatus.Status = ntStatus = STATUS_INVALID_PARAMETER; + Irp->IoStatus.Information = 0; + IoCompleteRequest (Irp, IO_NO_INCREMENT); + + return ntStatus; + } + + if ((Irp->AssociatedIrp.SystemBuffer + = MmGetSystemAddressForMdlSafe( + Irp->MdlAddress, + NormalPagePriority + ) ) == NULL + ) + { + DEBUGP (("[%s] Could not map address in IRP_MJ_READ\n", + MINIPORT_INSTANCE_ID (adapter))); + + NOTE_ERROR(); + Irp->IoStatus.Status = ntStatus = STATUS_INSUFFICIENT_RESOURCES; + Irp->IoStatus.Information = 0; + IoCompleteRequest (Irp, IO_NO_INCREMENT); + + return ntStatus; + } + + // BUGBUG!!! Use RemoveLock??? + + // + // Queue the IRP and return STATUS_PENDING. + // ---------------------------------------- + // Note: IoCsqInsertIrp marks the IRP pending. + // + + // BUGBUG!!! NDIS 5 implementation has IRP_QUEUE_SIZE of 16 and + // does not queue IRP if this capacity is exceeded. + // + // Is this needed??? + // + IoCsqInsertIrp(&adapter->PendingReadIrpQueue.CsqQueue, Irp, NULL); + + // Attempt to complete pending read IRPs from pending TAP + // send packet queue. + tapProcessSendPacketQueue(adapter); + + ntStatus = STATUS_PENDING; + + return ntStatus; +} + diff --git a/windows/TapDriver6/types.h b/windows/TapDriver6/types.h new file mode 100644 index 0000000..acea175 --- /dev/null +++ b/windows/TapDriver6/types.h @@ -0,0 +1,90 @@ +/* + * TAP-Windows -- A kernel driver to provide virtual tap + * device functionality on Windows. + * + * This code was inspired by the CIPE-Win32 driver by Damion K. Wilson. + * + * This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc., + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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 (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef TAP_TYPES_DEFINED +#define TAP_TYPES_DEFINED + +//typedef +//struct _Queue +//{ +// ULONG base; +// ULONG size; +// ULONG capacity; +// ULONG max_size; +// PVOID data[]; +//} Queue; + +//typedef struct _TAP_PACKET; + +//typedef struct _TapExtension +//{ +// // TAP device object and packet queues +// Queue *m_PacketQueue, *m_IrpQueue; +// PDEVICE_OBJECT m_TapDevice; +// NDIS_HANDLE m_TapDeviceHandle; +// ULONG TapFileIsOpen; +// +// // Used to lock packet queues +// NDIS_SPIN_LOCK m_QueueLock; +// BOOLEAN m_AllocatedSpinlocks; +// +// // Used to bracket open/close +// // state changes. +// MUTEX m_OpenCloseMutex; +// +// // True if device has been permanently halted +// BOOLEAN m_Halt; +// +// // TAP device name +// unsigned char *m_TapName; +// UNICODE_STRING m_UnicodeLinkName; +// BOOLEAN m_CreatedUnicodeLinkName; +// +// // Used for device status ioctl only +// const char *m_LastErrorFilename; +// int m_LastErrorLineNumber; +// LONG TapFileOpenCount; +// +// // Flags +// BOOLEAN TapDeviceCreated; +// BOOLEAN m_CalledTapDeviceFreeResources; +// +// // DPC queue for deferred packet injection +// BOOLEAN m_InjectDpcInitialized; +// KDPC m_InjectDpc; +// NDIS_SPIN_LOCK m_InjectLock; +// Queue *m_InjectQueue; +//} +//TapExtension, *TapExtensionPointer; + +typedef struct _InjectPacket + { +# define INJECT_PACKET_SIZE(data_size) (sizeof (InjectPacket) + (data_size)) +# define INJECT_PACKET_FREE(ib) NdisFreeMemory ((ib), INJECT_PACKET_SIZE ((ib)->m_Size), 0) + ULONG m_Size; + UCHAR m_Data []; // m_Data must be the last struct member + } +InjectPacket, *InjectPacketPointer; + +#endif diff --git a/windows/TapDriver6/zttap300.inf b/windows/TapDriver6/zttap300.inf new file mode 100644 index 0000000..f901b13 --- /dev/null +++ b/windows/TapDriver6/zttap300.inf @@ -0,0 +1,143 @@ +; +; ZeroTier One Virtual Network Port NDIS6 Driver +; +; Based on the OpenVPN tap-windows6 driver version 9.21.1 git +; commit 48f027cfca52b16b5fd23d82e6016ed8a91fc4d3. +; See: https://github.com/OpenVPN/tap-windows6 +; +; Modified by ZeroTier, Inc. - https://www.zerotier.com/ +; +; (1) Comment out 'tun' functionality and related features such as DHCP +; emulation, since we don't use any of that. Just want straight 'tap'. +; (2) Added custom IOCTL to enumerate L2 multicast memberships. +; (3) Increase maximum number of multicast memberships to 128. +; (4) Set default and max device MTU to 2800. +; (5) Rename/rebrand driver as ZeroTier network port driver. +; +; Original copyright below. Modifications released under GPLv2 as well. +; +; **************************************************************************** +; * Copyright (C) 2002-2014 OpenVPN Technologies, Inc. * +; * This program is free software; you can redistribute it and/or modify * +; * it under the terms of the GNU General Public License version 2 * +; * as published by the Free Software Foundation. * +; **************************************************************************** +; + +[Version] +Signature = "$Windows NT$" +CatalogFile = zttap300.cat +ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} +Provider = %Provider% +Class = Net +DriverVer=04/25/2015,3.00.00.0 + +[Strings] +DeviceDescription = "ZeroTier One Virtual Port" +Provider = "ZeroTier Networks LLC" ; We're ZeroTier, Inc. now but kernel mode certs are $300+ so fuqdat. + +; To build for x86, take NTamd64 off this and off the named section manually, build, then put it back! +[Manufacturer] +%Provider%=zttap300,NTamd64 + +[zttap300] +%DeviceDescription% = zttap300.ndi, root\zttap300 ; Root enumerated +%DeviceDescription% = zttap300.ndi, zttap300 ; Legacy + +[zttap300.NTamd64] +%DeviceDescription% = zttap300.ndi, root\zttap300 ; Root enumerated +%DeviceDescription% = zttap300.ndi, zttap300 ; Legacy + +;----------------- Characteristics ------------ +; NCF_PHYSICAL = 0x04 +; NCF_VIRTUAL = 0x01 +; NCF_SOFTWARE_ENUMERATED = 0x02 +; NCF_HIDDEN = 0x08 +; NCF_NO_SERVICE = 0x10 +; NCF_HAS_UI = 0x80 +;----------------- Characteristics ------------ +[zttap300.ndi] +CopyFiles = zttap300.driver, zttap300.files +AddReg = zttap300.reg +AddReg = zttap300.params.reg +Characteristics = 0x81 +*IfType = 0x6 ; IF_TYPE_ETHERNET_CSMACD +*MediaType = 0x0 ; NdisMedium802_3 +*PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 + +[zttap300.ndi.Services] +AddService = zttap300, 2, zttap300.service + +[zttap300.reg] +HKR, Ndi, Service, 0, "zttap300" +HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" ; yes, 'ndis5' is correct... yup, Windows. +HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" +HKR, , Manufacturer, 0, "%Provider%" +HKR, , ProductName, 0, "%DeviceDescription%" + +[zttap300.params.reg] +HKR, Ndi\params\MTU, ParamDesc, 0, "MTU" +HKR, Ndi\params\MTU, Type, 0, "int" +HKR, Ndi\params\MTU, Default, 0, "2800" +HKR, Ndi\params\MTU, Optional, 0, "0" +HKR, Ndi\params\MTU, Min, 0, "100" +HKR, Ndi\params\MTU, Max, 0, "2800" +HKR, Ndi\params\MTU, Step, 0, "1" +HKR, Ndi\params\MediaStatus, ParamDesc, 0, "Media Status" +HKR, Ndi\params\MediaStatus, Type, 0, "enum" +HKR, Ndi\params\MediaStatus, Default, 0, "0" +HKR, Ndi\params\MediaStatus, Optional, 0, "0" +HKR, Ndi\params\MediaStatus\enum, "0", 0, "Application Controlled" +HKR, Ndi\params\MediaStatus\enum, "1", 0, "Always Connected" +HKR, Ndi\params\MAC, ParamDesc, 0, "MAC Address" +HKR, Ndi\params\MAC, Type, 0, "edit" +HKR, Ndi\params\MAC, Optional, 0, "1" +HKR, Ndi\params\AllowNonAdmin, ParamDesc, 0, "Non-Admin Access" +HKR, Ndi\params\AllowNonAdmin, Type, 0, "enum" +HKR, Ndi\params\AllowNonAdmin, Default, 0, "0" +HKR, Ndi\params\AllowNonAdmin, Optional, 0, "0" +HKR, Ndi\params\AllowNonAdmin\enum, "0", 0, "Not Allowed" +HKR, Ndi\params\AllowNonAdmin\enum, "1", 0, "Allowed" + +;---------- Service Type ------------- +; SERVICE_KERNEL_DRIVER = 0x01 +; SERVICE_WIN32_OWN_PROCESS = 0x10 +;---------- Service Type ------------- + +;---------- Start Mode --------------- +; SERVICE_BOOT_START = 0x0 +; SERVICE_SYSTEM_START = 0x1 +; SERVICE_AUTO_START = 0x2 +; SERVICE_DEMAND_START = 0x3 +; SERVICE_DISABLED = 0x4 +;---------- Start Mode --------------- + +[zttap300.service] +DisplayName = %DeviceDescription% +ServiceType = 1 +StartType = 3 +ErrorControl = 1 +LoadOrderGroup = NDIS +ServiceBinary = %12%\zttap300.sys + +;----------------- Copy Flags ------------ +; COPYFLG_NOSKIP = 0x02 +; COPYFLG_NOVERSIONCHECK = 0x04 +;----------------- Copy Flags ------------ + +[SourceDisksNames] +1 = %DeviceDescription%, zttap300.sys + +[SourceDisksFiles] +zttap300.sys = 1 + +[DestinationDirs] +zttap300.files = 11 +zttap300.driver = 12 + +[zttap300.files] +; + +[zttap300.driver] +zttap300.sys,,,6 ; COPYFLG_NOSKIP | COPYFLG_NOVERSIONCHECK + diff --git a/windows/WinUI/APIHandler.cs b/windows/WinUI/APIHandler.cs new file mode 100644 index 0000000..419a11c --- /dev/null +++ b/windows/WinUI/APIHandler.cs @@ -0,0 +1,419 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Net; +using System.IO; +using System.Windows; +using Newtonsoft.Json; +using System.Diagnostics; + +namespace WinUI +{ + + + public class APIHandler + { + private string authtoken; + + private string url = null; + + private static volatile APIHandler instance; + private static object syncRoot = new Object(); + + public delegate void NetworkListCallback(List networks); + public delegate void StatusCallback(ZeroTierStatus status); + + public static APIHandler Instance + { + get + { + if (instance == null) + { + lock (syncRoot) + { + if (instance == null) + { + if (!initHandler()) + { + return null; + } + } + } + } + + return instance; + } + } + + private static bool initHandler(bool resetToken = false) + { + String localZtDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\ZeroTier\\One"; + String globalZtDir = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\ZeroTier\\One"; + + String authToken = ""; + Int32 port = 9993; + + if (resetToken) + { + instance = null; + if (File.Exists(localZtDir + "\\authtoken.secret")) + { + File.Delete(localZtDir + "\\authtoken.secret"); + } + + if (File.Exists(localZtDir + "\\zerotier-one.port")) + { + File.Delete(localZtDir + "\\zerotier-one.port"); + } + } + + if (!File.Exists(localZtDir + "\\authtoken.secret") || !File.Exists(localZtDir + "\\zerotier-one.port")) + { + // launch external process to copy file into place + String curPath = System.Reflection.Assembly.GetEntryAssembly().Location; + int index = curPath.LastIndexOf("\\"); + curPath = curPath.Substring(0, index); + ProcessStartInfo startInfo = new ProcessStartInfo(curPath + "\\copyutil.exe", "\""+globalZtDir+"\"" + " " + "\""+localZtDir+"\""); + startInfo.Verb = "runas"; + + + var process = Process.Start(startInfo); + process.WaitForExit(); + } + + authToken = readAuthToken(localZtDir + "\\authtoken.secret"); + + if ((authToken == null) || (authToken.Length <= 0)) + { + MessageBox.Show("Unable to read ZeroTier One authtoken", "ZeroTier One"); + return false; + } + + port = readPort(localZtDir + "\\zerotier-one.port"); + instance = new APIHandler(port, authToken); + return true; + } + + private static String readAuthToken(String path) + { + String authToken = ""; + + if (File.Exists(path)) + { + try + { + byte[] tmp = File.ReadAllBytes(path); + authToken = System.Text.Encoding.UTF8.GetString(tmp).Trim(); + } + catch + { + MessageBox.Show("Unable to read ZeroTier One Auth Token from:\r\n" + path, "ZeroTier One"); + } + } + + return authToken; + } + + private static Int32 readPort(String path) + { + Int32 port = 9993; + + try + { + byte[] tmp = File.ReadAllBytes(path); + port = Int32.Parse(System.Text.Encoding.ASCII.GetString(tmp).Trim()); + if ((port <= 0) || (port > 65535)) + port = 9993; + } + catch + { + } + + return port; + } + + private APIHandler() + { + url = "http://127.0.0.1:9993"; + } + + public APIHandler(int port, string authtoken) + { + url = "http://127.0.0.1:" + port; + this.authtoken = authtoken; + } + + + + public void GetStatus(StatusCallback cb) + { + var request = WebRequest.Create(url + "/status" + "?auth=" + authtoken) as HttpWebRequest; + if (request != null) + { + request.Method = "GET"; + request.ContentType = "application/json"; + } + + try + { + var httpResponse = (HttpWebResponse)request.GetResponse(); + if (httpResponse.StatusCode == HttpStatusCode.OK) + { + using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) + { + var responseText = streamReader.ReadToEnd(); + + ZeroTierStatus status = null; + try + { + status = JsonConvert.DeserializeObject(responseText); + } + catch (JsonReaderException e) + { + Console.WriteLine(e.ToString()); + } + cb(status); + } + } + else if (httpResponse.StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + } + catch (System.Net.Sockets.SocketException) + { + cb(null); + } + catch (System.Net.WebException e) + { + if (((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + else + { + cb(null); + } + } + } + + + + public void GetNetworks(NetworkListCallback cb) + { + var request = WebRequest.Create(url + "/network" + "?auth=" + authtoken) as HttpWebRequest; + if (request == null) + { + cb(null); + } + + request.Method = "GET"; + request.ContentType = "application/json"; + request.Timeout = 10000; + + try + { + var httpResponse = (HttpWebResponse)request.GetResponse(); + + if (httpResponse.StatusCode == HttpStatusCode.OK) + { + using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) + { + var responseText = streamReader.ReadToEnd(); + + List networkList = null; + try + { + networkList = JsonConvert.DeserializeObject>(responseText); + foreach (ZeroTierNetwork n in networkList) + { + // all networks received via JSON are connected by definition + n.IsConnected = true; + } + } + catch (JsonReaderException e) + { + Console.WriteLine(e.ToString()); + } + cb(networkList); + } + } + else if (httpResponse.StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + } + catch (System.Net.Sockets.SocketException) + { + cb(null); + } + catch (System.Net.WebException e) + { + if (((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + else + { + cb(null); + } + } + } + + public void JoinNetwork(string nwid, bool allowManaged = true, bool allowGlobal = false, bool allowDefault = false) + { + var request = WebRequest.Create(url + "/network/" + nwid + "?auth=" + authtoken) as HttpWebRequest; + if (request == null) + { + return; + } + + request.Method = "POST"; + request.ContentType = "applicaiton/json"; + request.Timeout = 10000; + try + { + using (var streamWriter = new StreamWriter(((HttpWebRequest)request).GetRequestStream())) + { + string json = "{\"allowManaged\":" + (allowManaged ? "true" : "false") + "," + + "\"allowGlobal\":" + (allowGlobal ? "true" : "false") + "," + + "\"allowDefault\":" + (allowDefault ? "true" : "false") + "}"; + streamWriter.Write(json); + streamWriter.Flush(); + streamWriter.Close(); + } + } + catch (System.Net.WebException) + { + MessageBox.Show("Error Joining Network: Cannot connect to ZeroTier service."); + return; + } + + try + { + var httpResponse = (HttpWebResponse)request.GetResponse(); + + if (httpResponse.StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + else if (httpResponse.StatusCode != HttpStatusCode.OK) + { + Console.WriteLine("Error sending join network message"); + } + } + catch (System.Net.Sockets.SocketException) + { + MessageBox.Show("Error Joining Network: Cannot connect to ZeroTier service."); + } + catch (System.Net.WebException e) + { + if (((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + MessageBox.Show("Error Joining Network: Cannot connect to ZeroTier service."); + } + } + + public void LeaveNetwork(string nwid) + { + var request = WebRequest.Create(url + "/network/" + nwid + "?auth=" + authtoken) as HttpWebRequest; + if (request == null) + { + return; + } + + request.Method = "DELETE"; + request.Timeout = 10000; + + try + { + var httpResponse = (HttpWebResponse)request.GetResponse(); + + if (httpResponse.StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + else if (httpResponse.StatusCode != HttpStatusCode.OK) + { + Console.WriteLine("Error sending leave network message"); + } + } + catch (System.Net.Sockets.SocketException) + { + MessageBox.Show("Error Leaving Network: Cannot connect to ZeroTier service."); + } + catch (System.Net.WebException e) + { + if (((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + MessageBox.Show("Error Leaving Network: Cannot connect to ZeroTier service."); + } + catch + { + Console.WriteLine("Error leaving network: Unknown error"); + } + } + + public delegate void PeersCallback(List peers); + + public void GetPeers(PeersCallback cb) + { + var request = WebRequest.Create(url + "/peer" + "?auth=" + authtoken) as HttpWebRequest; + if (request == null) + { + cb(null); + } + + request.Method = "GET"; + request.ContentType = "application/json"; + + try + { + var httpResponse = (HttpWebResponse)request.GetResponse(); + if (httpResponse.StatusCode == HttpStatusCode.OK) + { + using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) + { + var responseText = streamReader.ReadToEnd(); + //Console.WriteLine(responseText); + List peerList = null; + try + { + peerList = JsonConvert.DeserializeObject>(responseText); + } + catch (JsonReaderException e) + { + Console.WriteLine(e.ToString()); + } + cb(peerList); + } + } + else if (httpResponse.StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + } + catch (System.Net.Sockets.SocketException) + { + cb(null); + } + catch (System.Net.WebException e) + { + if (((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.Unauthorized) + { + APIHandler.initHandler(true); + } + else + { + cb(null); + } + } + } + } +} diff --git a/windows/WinUI/AboutView.xaml b/windows/WinUI/AboutView.xaml new file mode 100644 index 0000000..f207af4 --- /dev/null +++ b/windows/WinUI/AboutView.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/windows/WinUI/AboutView.xaml.cs b/windows/WinUI/AboutView.xaml.cs new file mode 100644 index 0000000..9c48493 --- /dev/null +++ b/windows/WinUI/AboutView.xaml.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace WinUI +{ + /// + /// Interaction logic for AboutView.xaml + /// + public partial class AboutView : Window + { + public AboutView() + { + InitializeComponent(); + } + + private void Hyperlink_MouseLeftButtonDown(object sender, RequestNavigateEventArgs e) + { + var hyperlink = (Hyperlink)sender; + Process.Start(hyperlink.NavigateUri.ToString()); + } + } +} diff --git a/windows/WinUI/App.config b/windows/WinUI/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/windows/WinUI/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/windows/WinUI/App.xaml b/windows/WinUI/App.xaml new file mode 100644 index 0000000..12ed85f --- /dev/null +++ b/windows/WinUI/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/windows/WinUI/App.xaml.cs b/windows/WinUI/App.xaml.cs new file mode 100644 index 0000000..53ef2f6 --- /dev/null +++ b/windows/WinUI/App.xaml.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using Hardcodet.Wpf.TaskbarNotification; + +namespace WinUI +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + private TaskbarIcon tb; + + private void InitApplication() + { + tb = (TaskbarIcon)FindResource("NotifyIcon"); + tb.Visibility = Visibility.Visible; + } + } +} diff --git a/windows/WinUI/Fonts/segoeui.ttf b/windows/WinUI/Fonts/segoeui.ttf new file mode 100644 index 0000000..fc18ebd Binary files /dev/null and b/windows/WinUI/Fonts/segoeui.ttf differ diff --git a/windows/WinUI/Fonts/segoeuib.ttf b/windows/WinUI/Fonts/segoeuib.ttf new file mode 100644 index 0000000..5f31e0c Binary files /dev/null and b/windows/WinUI/Fonts/segoeuib.ttf differ diff --git a/windows/WinUI/Fonts/segoeuii.ttf b/windows/WinUI/Fonts/segoeuii.ttf new file mode 100644 index 0000000..7efb70d Binary files /dev/null and b/windows/WinUI/Fonts/segoeuii.ttf differ diff --git a/windows/WinUI/Fonts/segoeuiz.ttf b/windows/WinUI/Fonts/segoeuiz.ttf new file mode 100644 index 0000000..d7bb186 Binary files /dev/null and b/windows/WinUI/Fonts/segoeuiz.ttf differ diff --git a/windows/WinUI/JoinNetworkView.xaml b/windows/WinUI/JoinNetworkView.xaml new file mode 100644 index 0000000..1cd1e98 --- /dev/null +++ b/windows/WinUI/JoinNetworkView.xaml @@ -0,0 +1,16 @@ + + + + + + +